Skip to content

Commit 504b01b

Browse files
bripkensMylesBorins
authored andcommitted
v8,src: expose statistics about heap spaces
Provide means to inspect information about the separate heap spaces via a callable API. This is helpful to analyze memory issues. Fixes: #2079 PR-URL: #4463 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Trevor Norris <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 20bee0f commit 504b01b

File tree

6 files changed

+202
-2
lines changed

6 files changed

+202
-2
lines changed

doc/api/v8.md

+49
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,55 @@ Returns an object with the following properties
2424
}
2525
```
2626

27+
## getHeapSpaceStatistics()
28+
29+
Returns statistics about the V8 heap spaces, i.e. the segments which make up
30+
the V8 heap. Order of heap spaces nor availability of a heap space can be
31+
guaranteed as the statistics are provided via the V8 `GetHeapSpaceStatistics`
32+
function.
33+
34+
Example result:
35+
36+
```
37+
[
38+
{
39+
"space_name": "new_space",
40+
"space_size": 2063872,
41+
"space_used_size": 951112,
42+
"space_available_size": 80824,
43+
"physical_space_size": 2063872
44+
},
45+
{
46+
"space_name": "old_space",
47+
"space_size": 3090560,
48+
"space_used_size": 2493792,
49+
"space_available_size": 0,
50+
"physical_space_size": 3090560
51+
},
52+
{
53+
"space_name": "code_space",
54+
"space_size": 1260160,
55+
"space_used_size": 644256,
56+
"space_available_size": 960,
57+
"physical_space_size": 1260160
58+
},
59+
{
60+
"space_name": "map_space",
61+
"space_size": 1094160,
62+
"space_used_size": 201608,
63+
"space_available_size": 0,
64+
"physical_space_size": 1094160
65+
},
66+
{
67+
"space_name": "large_object_space",
68+
"space_size": 0,
69+
"space_used_size": 0,
70+
"space_available_size": 1490980608,
71+
"physical_space_size": 0
72+
}
73+
]
74+
```
75+
2776
## setFlagsFromString(string)
2877
<!-- YAML
2978
added: v1.0.0

lib/v8.js

+32-1
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,28 @@
1616

1717
const v8binding = process.binding('v8');
1818

19+
// Properties for heap statistics buffer extraction.
1920
const heapStatisticsBuffer =
2021
new Uint32Array(v8binding.heapStatisticsArrayBuffer);
21-
2222
const kTotalHeapSizeIndex = v8binding.kTotalHeapSizeIndex;
2323
const kTotalHeapSizeExecutableIndex = v8binding.kTotalHeapSizeExecutableIndex;
2424
const kTotalPhysicalSizeIndex = v8binding.kTotalPhysicalSizeIndex;
2525
const kTotalAvailableSize = v8binding.kTotalAvailableSize;
2626
const kUsedHeapSizeIndex = v8binding.kUsedHeapSizeIndex;
2727
const kHeapSizeLimitIndex = v8binding.kHeapSizeLimitIndex;
2828

29+
// Properties for heap space statistics buffer extraction.
30+
const heapSpaceStatisticsBuffer =
31+
new Uint32Array(v8binding.heapSpaceStatisticsArrayBuffer);
32+
const kHeapSpaces = v8binding.kHeapSpaces;
33+
const kNumberOfHeapSpaces = kHeapSpaces.length;
34+
const kHeapSpaceStatisticsPropertiesCount =
35+
v8binding.kHeapSpaceStatisticsPropertiesCount;
36+
const kSpaceSizeIndex = v8binding.kSpaceSizeIndex;
37+
const kSpaceUsedSizeIndex = v8binding.kSpaceUsedSizeIndex;
38+
const kSpaceAvailableSizeIndex = v8binding.kSpaceAvailableSizeIndex;
39+
const kPhysicalSpaceSizeIndex = v8binding.kPhysicalSpaceSizeIndex;
40+
2941
exports.getHeapStatistics = function() {
3042
const buffer = heapStatisticsBuffer;
3143

@@ -42,3 +54,22 @@ exports.getHeapStatistics = function() {
4254
};
4355

4456
exports.setFlagsFromString = v8binding.setFlagsFromString;
57+
58+
exports.getHeapSpaceStatistics = function() {
59+
const heapSpaceStatistics = new Array(kNumberOfHeapSpaces);
60+
const buffer = heapSpaceStatisticsBuffer;
61+
v8binding.updateHeapSpaceStatisticsArrayBuffer();
62+
63+
for (var i = 0; i < kNumberOfHeapSpaces; i++) {
64+
const propertyOffset = i * kHeapSpaceStatisticsPropertiesCount;
65+
heapSpaceStatistics[i] = {
66+
space_name: kHeapSpaces[i],
67+
space_size: buffer[propertyOffset + kSpaceSizeIndex],
68+
space_used_size: buffer[propertyOffset + kSpaceUsedSizeIndex],
69+
space_available_size: buffer[propertyOffset + kSpaceAvailableSizeIndex],
70+
physical_space_size: buffer[propertyOffset + kPhysicalSpaceSizeIndex]
71+
};
72+
}
73+
74+
return heapSpaceStatistics;
75+
};

src/env-inl.h

+12
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ inline Environment::~Environment() {
242242
isolate_data()->Put();
243243

244244
delete[] heap_statistics_buffer_;
245+
delete[] heap_space_statistics_buffer_;
245246
delete[] http_parser_buffer_;
246247
}
247248

@@ -387,6 +388,17 @@ inline void Environment::set_heap_statistics_buffer(uint32_t* pointer) {
387388
heap_statistics_buffer_ = pointer;
388389
}
389390

391+
inline uint32_t* Environment::heap_space_statistics_buffer() const {
392+
CHECK_NE(heap_space_statistics_buffer_, nullptr);
393+
return heap_space_statistics_buffer_;
394+
}
395+
396+
inline void Environment::set_heap_space_statistics_buffer(uint32_t* pointer) {
397+
CHECK_EQ(heap_space_statistics_buffer_, nullptr); // Should be set only once.
398+
heap_space_statistics_buffer_ = pointer;
399+
}
400+
401+
390402
inline char* Environment::http_parser_buffer() const {
391403
return http_parser_buffer_;
392404
}

src/env.h

+4
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,9 @@ class Environment {
460460
inline uint32_t* heap_statistics_buffer() const;
461461
inline void set_heap_statistics_buffer(uint32_t* pointer);
462462

463+
inline uint32_t* heap_space_statistics_buffer() const;
464+
inline void set_heap_space_statistics_buffer(uint32_t* pointer);
465+
463466
inline char* http_parser_buffer() const;
464467
inline void set_http_parser_buffer(char* buffer);
465468

@@ -563,6 +566,7 @@ class Environment {
563566
int handle_cleanup_waiting_;
564567

565568
uint32_t* heap_statistics_buffer_ = nullptr;
569+
uint32_t* heap_space_statistics_buffer_ = nullptr;
566570

567571
char* http_parser_buffer_;
568572

src/node_v8.cc

+86-1
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77

88
namespace node {
99

10+
using v8::Array;
1011
using v8::ArrayBuffer;
1112
using v8::Context;
1213
using v8::Function;
1314
using v8::FunctionCallbackInfo;
15+
using v8::HeapSpaceStatistics;
1416
using v8::HeapStatistics;
1517
using v8::Isolate;
1618
using v8::Local;
19+
using v8::NewStringType;
1720
using v8::Object;
1821
using v8::String;
1922
using v8::Uint32;
@@ -34,6 +37,21 @@ static const size_t kHeapStatisticsPropertiesCount =
3437
HEAP_STATISTICS_PROPERTIES(V);
3538
#undef V
3639

40+
#define HEAP_SPACE_STATISTICS_PROPERTIES(V) \
41+
V(0, space_size, kSpaceSizeIndex) \
42+
V(1, space_used_size, kSpaceUsedSizeIndex) \
43+
V(2, space_available_size, kSpaceAvailableSizeIndex) \
44+
V(3, physical_space_size, kPhysicalSpaceSizeIndex)
45+
46+
#define V(a, b, c) +1
47+
static const size_t kHeapSpaceStatisticsPropertiesCount =
48+
HEAP_SPACE_STATISTICS_PROPERTIES(V);
49+
#undef V
50+
51+
// Will be populated in InitializeV8Bindings.
52+
static size_t number_of_heap_spaces = 0;
53+
54+
3755
void UpdateHeapStatisticsArrayBuffer(const FunctionCallbackInfo<Value>& args) {
3856
Environment* env = Environment::GetCurrent(args);
3957
HeapStatistics s;
@@ -45,6 +63,23 @@ void UpdateHeapStatisticsArrayBuffer(const FunctionCallbackInfo<Value>& args) {
4563
}
4664

4765

66+
void UpdateHeapSpaceStatisticsBuffer(const FunctionCallbackInfo<Value>& args) {
67+
Environment* env = Environment::GetCurrent(args);
68+
HeapSpaceStatistics s;
69+
Isolate* const isolate = env->isolate();
70+
uint32_t* buffer = env->heap_space_statistics_buffer();
71+
72+
for (size_t i = 0; i < number_of_heap_spaces; i++) {
73+
isolate->GetHeapSpaceStatistics(&s, i);
74+
size_t const property_offset = i * kHeapSpaceStatisticsPropertiesCount;
75+
#define V(index, name, _) buffer[property_offset + index] = \
76+
static_cast<uint32_t>(s.name());
77+
HEAP_SPACE_STATISTICS_PROPERTIES(V)
78+
#undef V
79+
}
80+
}
81+
82+
4883
void SetFlagsFromString(const FunctionCallbackInfo<Value>& args) {
4984
Environment* env = Environment::GetCurrent(args);
5085

@@ -62,10 +97,10 @@ void InitializeV8Bindings(Local<Object> target,
6297
Local<Value> unused,
6398
Local<Context> context) {
6499
Environment* env = Environment::GetCurrent(context);
100+
65101
env->SetMethod(target,
66102
"updateHeapStatisticsArrayBuffer",
67103
UpdateHeapStatisticsArrayBuffer);
68-
env->SetMethod(target, "setFlagsFromString", SetFlagsFromString);
69104

70105
env->set_heap_statistics_buffer(new uint32_t[kHeapStatisticsPropertiesCount]);
71106

@@ -84,6 +119,56 @@ void InitializeV8Bindings(Local<Object> target,
84119

85120
HEAP_STATISTICS_PROPERTIES(V)
86121
#undef V
122+
123+
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(),
124+
"kHeapSpaceStatisticsPropertiesCount"),
125+
Uint32::NewFromUnsigned(env->isolate(),
126+
kHeapSpaceStatisticsPropertiesCount));
127+
128+
number_of_heap_spaces = env->isolate()->NumberOfHeapSpaces();
129+
130+
// Heap space names are extracted once and exposed to JavaScript to
131+
// avoid excessive creation of heap space name Strings.
132+
HeapSpaceStatistics s;
133+
const Local<Array> heap_spaces = Array::New(env->isolate(),
134+
number_of_heap_spaces);
135+
for (size_t i = 0; i < number_of_heap_spaces; i++) {
136+
env->isolate()->GetHeapSpaceStatistics(&s, i);
137+
Local<String> heap_space_name = String::NewFromUtf8(env->isolate(),
138+
s.space_name(),
139+
NewStringType::kNormal)
140+
.ToLocalChecked();
141+
heap_spaces->Set(i, heap_space_name);
142+
}
143+
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kHeapSpaces"),
144+
heap_spaces);
145+
146+
env->SetMethod(target,
147+
"updateHeapSpaceStatisticsArrayBuffer",
148+
UpdateHeapSpaceStatisticsBuffer);
149+
150+
env->set_heap_space_statistics_buffer(
151+
new uint32_t[kHeapSpaceStatisticsPropertiesCount * number_of_heap_spaces]);
152+
153+
const size_t heap_space_statistics_buffer_byte_length =
154+
sizeof(*env->heap_space_statistics_buffer()) *
155+
kHeapSpaceStatisticsPropertiesCount *
156+
number_of_heap_spaces;
157+
158+
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(),
159+
"heapSpaceStatisticsArrayBuffer"),
160+
ArrayBuffer::New(env->isolate(),
161+
env->heap_space_statistics_buffer(),
162+
heap_space_statistics_buffer_byte_length));
163+
164+
#define V(i, _, name) \
165+
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), #name), \
166+
Uint32::NewFromUnsigned(env->isolate(), i));
167+
168+
HEAP_SPACE_STATISTICS_PROPERTIES(V)
169+
#undef V
170+
171+
env->SetMethod(target, "setFlagsFromString", SetFlagsFromString);
87172
}
88173

89174
} // namespace node

test/parallel/test-v8-stats.js

+19
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,22 @@ assert.deepEqual(Object.keys(s).sort(), keys);
1515
keys.forEach(function(key) {
1616
assert.equal(typeof s[key], 'number');
1717
});
18+
19+
20+
const expectedHeapSpaces = [
21+
'new_space',
22+
'old_space',
23+
'code_space',
24+
'map_space',
25+
'large_object_space'
26+
];
27+
const heapSpaceStatistics = v8.getHeapSpaceStatistics();
28+
const actualHeapSpaceNames = heapSpaceStatistics.map((s) => s.space_name);
29+
assert.deepEqual(actualHeapSpaceNames.sort(), expectedHeapSpaces.sort());
30+
heapSpaceStatistics.forEach((heapSpace) => {
31+
assert.strictEqual(typeof heapSpace.space_name, 'string');
32+
assert.strictEqual(typeof heapSpace.space_size, 'number');
33+
assert.strictEqual(typeof heapSpace.space_used_size, 'number');
34+
assert.strictEqual(typeof heapSpace.space_available_size, 'number');
35+
assert.strictEqual(typeof heapSpace.physical_space_size, 'number');
36+
});

0 commit comments

Comments
 (0)