From 5f57005ec9eacbfc7855ec7c9e246f61da5a75ae Mon Sep 17 00:00:00 2001 From: Ben Ripkens Date: Tue, 29 Dec 2015 11:54:35 +0100 Subject: [PATCH] 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: https://github.com/nodejs/node/issues/2079 PR-URL: https://github.com/nodejs/node/pull/4463 Reviewed-By: Colin Ihrig Reviewed-By: Trevor Norris Reviewed-By: Ben Noordhuis Reviewed-By: James M Snell --- doc/api/v8.markdown | 49 +++++++++++++++++++ lib/v8.js | 33 ++++++++++++- src/env-inl.h | 12 +++++ src/env.h | 4 ++ src/node_v8.cc | 87 +++++++++++++++++++++++++++++++++- test/parallel/test-v8-stats.js | 19 ++++++++ 6 files changed, 202 insertions(+), 2 deletions(-) diff --git a/doc/api/v8.markdown b/doc/api/v8.markdown index 70abd6c6473f24..c6d760b0d5ff31 100644 --- a/doc/api/v8.markdown +++ b/doc/api/v8.markdown @@ -21,6 +21,55 @@ Returns an object with the following properties } ``` +## getHeapSpaceStatistics() + +Returns statistics about the V8 heap spaces, i.e. the segments which make up +the V8 heap. Order of heap spaces nor availability of a heap space can be +guaranteed as the statistics are provided via the V8 `GetHeapSpaceStatistics` +function. + +Example result: + +``` +[ + { + "space_name": "new_space", + "space_size": 2063872, + "space_used_size": 951112, + "space_available_size": 80824, + "physical_space_size": 2063872 + }, + { + "space_name": "old_space", + "space_size": 3090560, + "space_used_size": 2493792, + "space_available_size": 0, + "physical_space_size": 3090560 + }, + { + "space_name": "code_space", + "space_size": 1260160, + "space_used_size": 644256, + "space_available_size": 960, + "physical_space_size": 1260160 + }, + { + "space_name": "map_space", + "space_size": 1094160, + "space_used_size": 201608, + "space_available_size": 0, + "physical_space_size": 1094160 + }, + { + "space_name": "large_object_space", + "space_size": 0, + "space_used_size": 0, + "space_available_size": 1490980608, + "physical_space_size": 0 + } +] +``` + ## setFlagsFromString(string) Set additional V8 command line flags. Use with care; changing settings diff --git a/lib/v8.js b/lib/v8.js index acadfa64e0650e..551b2ada98526e 100644 --- a/lib/v8.js +++ b/lib/v8.js @@ -16,9 +16,9 @@ const v8binding = process.binding('v8'); +// Properties for heap statistics buffer extraction. const heapStatisticsBuffer = new Uint32Array(v8binding.heapStatisticsArrayBuffer); - const kTotalHeapSizeIndex = v8binding.kTotalHeapSizeIndex; const kTotalHeapSizeExecutableIndex = v8binding.kTotalHeapSizeExecutableIndex; const kTotalPhysicalSizeIndex = v8binding.kTotalPhysicalSizeIndex; @@ -26,6 +26,18 @@ const kTotalAvailableSize = v8binding.kTotalAvailableSize; const kUsedHeapSizeIndex = v8binding.kUsedHeapSizeIndex; const kHeapSizeLimitIndex = v8binding.kHeapSizeLimitIndex; +// Properties for heap space statistics buffer extraction. +const heapSpaceStatisticsBuffer = + new Uint32Array(v8binding.heapSpaceStatisticsArrayBuffer); +const kHeapSpaces = v8binding.kHeapSpaces; +const kNumberOfHeapSpaces = kHeapSpaces.length; +const kHeapSpaceStatisticsPropertiesCount = + v8binding.kHeapSpaceStatisticsPropertiesCount; +const kSpaceSizeIndex = v8binding.kSpaceSizeIndex; +const kSpaceUsedSizeIndex = v8binding.kSpaceUsedSizeIndex; +const kSpaceAvailableSizeIndex = v8binding.kSpaceAvailableSizeIndex; +const kPhysicalSpaceSizeIndex = v8binding.kPhysicalSpaceSizeIndex; + exports.getHeapStatistics = function() { const buffer = heapStatisticsBuffer; @@ -42,3 +54,22 @@ exports.getHeapStatistics = function() { }; exports.setFlagsFromString = v8binding.setFlagsFromString; + +exports.getHeapSpaceStatistics = function() { + const heapSpaceStatistics = new Array(kNumberOfHeapSpaces); + const buffer = heapSpaceStatisticsBuffer; + v8binding.updateHeapSpaceStatisticsArrayBuffer(); + + for (let i = 0; i < kNumberOfHeapSpaces; i++) { + const propertyOffset = i * kHeapSpaceStatisticsPropertiesCount; + heapSpaceStatistics[i] = { + space_name: kHeapSpaces[i], + space_size: buffer[propertyOffset + kSpaceSizeIndex], + space_used_size: buffer[propertyOffset + kSpaceUsedSizeIndex], + space_available_size: buffer[propertyOffset + kSpaceAvailableSizeIndex], + physical_space_size: buffer[propertyOffset + kPhysicalSpaceSizeIndex] + }; + } + + return heapSpaceStatistics; +}; diff --git a/src/env-inl.h b/src/env-inl.h index f73e9c6ba2000a..d2c0e048a6d3e3 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -242,6 +242,7 @@ inline Environment::~Environment() { isolate_data()->Put(); delete[] heap_statistics_buffer_; + delete[] heap_space_statistics_buffer_; delete[] http_parser_buffer_; } @@ -374,6 +375,17 @@ inline void Environment::set_heap_statistics_buffer(uint32_t* pointer) { heap_statistics_buffer_ = pointer; } +inline uint32_t* Environment::heap_space_statistics_buffer() const { + CHECK_NE(heap_space_statistics_buffer_, nullptr); + return heap_space_statistics_buffer_; +} + +inline void Environment::set_heap_space_statistics_buffer(uint32_t* pointer) { + CHECK_EQ(heap_space_statistics_buffer_, nullptr); // Should be set only once. + heap_space_statistics_buffer_ = pointer; +} + + inline char* Environment::http_parser_buffer() const { return http_parser_buffer_; } diff --git a/src/env.h b/src/env.h index 743bf057e8584d..bb0868e1d83140 100644 --- a/src/env.h +++ b/src/env.h @@ -462,6 +462,9 @@ class Environment { inline uint32_t* heap_statistics_buffer() const; inline void set_heap_statistics_buffer(uint32_t* pointer); + inline uint32_t* heap_space_statistics_buffer() const; + inline void set_heap_space_statistics_buffer(uint32_t* pointer); + inline char* http_parser_buffer() const; inline void set_http_parser_buffer(char* buffer); @@ -562,6 +565,7 @@ class Environment { int handle_cleanup_waiting_; uint32_t* heap_statistics_buffer_ = nullptr; + uint32_t* heap_space_statistics_buffer_ = nullptr; char* http_parser_buffer_; diff --git a/src/node_v8.cc b/src/node_v8.cc index 9f456daec464ce..a1122e57f13cac 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -7,13 +7,16 @@ namespace node { +using v8::Array; using v8::ArrayBuffer; using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; +using v8::HeapSpaceStatistics; using v8::HeapStatistics; using v8::Isolate; using v8::Local; +using v8::NewStringType; using v8::Object; using v8::String; using v8::Uint32; @@ -34,6 +37,21 @@ static const size_t kHeapStatisticsPropertiesCount = HEAP_STATISTICS_PROPERTIES(V); #undef V +#define HEAP_SPACE_STATISTICS_PROPERTIES(V) \ + V(0, space_size, kSpaceSizeIndex) \ + V(1, space_used_size, kSpaceUsedSizeIndex) \ + V(2, space_available_size, kSpaceAvailableSizeIndex) \ + V(3, physical_space_size, kPhysicalSpaceSizeIndex) + +#define V(a, b, c) +1 +static const size_t kHeapSpaceStatisticsPropertiesCount = + HEAP_SPACE_STATISTICS_PROPERTIES(V); +#undef V + +// Will be populated in InitializeV8Bindings. +static size_t number_of_heap_spaces = 0; + + void UpdateHeapStatisticsArrayBuffer(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); HeapStatistics s; @@ -45,6 +63,23 @@ void UpdateHeapStatisticsArrayBuffer(const FunctionCallbackInfo& args) { } +void UpdateHeapSpaceStatisticsBuffer(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + HeapSpaceStatistics s; + Isolate* const isolate = env->isolate(); + uint32_t* buffer = env->heap_space_statistics_buffer(); + + for (size_t i = 0; i < number_of_heap_spaces; i++) { + isolate->GetHeapSpaceStatistics(&s, i); + size_t const property_offset = i * kHeapSpaceStatisticsPropertiesCount; +#define V(index, name, _) buffer[property_offset + index] = \ + static_cast(s.name()); + HEAP_SPACE_STATISTICS_PROPERTIES(V) +#undef V + } +} + + void SetFlagsFromString(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -62,10 +97,10 @@ void InitializeV8Bindings(Local target, Local unused, Local context) { Environment* env = Environment::GetCurrent(context); + env->SetMethod(target, "updateHeapStatisticsArrayBuffer", UpdateHeapStatisticsArrayBuffer); - env->SetMethod(target, "setFlagsFromString", SetFlagsFromString); env->set_heap_statistics_buffer(new uint32_t[kHeapStatisticsPropertiesCount]); @@ -84,6 +119,56 @@ void InitializeV8Bindings(Local target, HEAP_STATISTICS_PROPERTIES(V) #undef V + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), + "kHeapSpaceStatisticsPropertiesCount"), + Uint32::NewFromUnsigned(env->isolate(), + kHeapSpaceStatisticsPropertiesCount)); + + number_of_heap_spaces = env->isolate()->NumberOfHeapSpaces(); + + // Heap space names are extracted once and exposed to JavaScript to + // avoid excessive creation of heap space name Strings. + HeapSpaceStatistics s; + const Local heap_spaces = Array::New(env->isolate(), + number_of_heap_spaces); + for (size_t i = 0; i < number_of_heap_spaces; i++) { + env->isolate()->GetHeapSpaceStatistics(&s, i); + Local heap_space_name = String::NewFromUtf8(env->isolate(), + s.space_name(), + NewStringType::kNormal) + .ToLocalChecked(); + heap_spaces->Set(i, heap_space_name); + } + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kHeapSpaces"), + heap_spaces); + + env->SetMethod(target, + "updateHeapSpaceStatisticsArrayBuffer", + UpdateHeapSpaceStatisticsBuffer); + + env->set_heap_space_statistics_buffer( + new uint32_t[kHeapSpaceStatisticsPropertiesCount * number_of_heap_spaces]); + + const size_t heap_space_statistics_buffer_byte_length = + sizeof(*env->heap_space_statistics_buffer()) * + kHeapSpaceStatisticsPropertiesCount * + number_of_heap_spaces; + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), + "heapSpaceStatisticsArrayBuffer"), + ArrayBuffer::New(env->isolate(), + env->heap_space_statistics_buffer(), + heap_space_statistics_buffer_byte_length)); + +#define V(i, _, name) \ + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), #name), \ + Uint32::NewFromUnsigned(env->isolate(), i)); + + HEAP_SPACE_STATISTICS_PROPERTIES(V) +#undef V + + env->SetMethod(target, "setFlagsFromString", SetFlagsFromString); } } // namespace node diff --git a/test/parallel/test-v8-stats.js b/test/parallel/test-v8-stats.js index eb5566fe2bc28f..54140e4110fefe 100644 --- a/test/parallel/test-v8-stats.js +++ b/test/parallel/test-v8-stats.js @@ -15,3 +15,22 @@ assert.deepEqual(Object.keys(s).sort(), keys); keys.forEach(function(key) { assert.equal(typeof s[key], 'number'); }); + + +const expectedHeapSpaces = [ + 'new_space', + 'old_space', + 'code_space', + 'map_space', + 'large_object_space' +]; +const heapSpaceStatistics = v8.getHeapSpaceStatistics(); +const actualHeapSpaceNames = heapSpaceStatistics.map(s => s.space_name); +assert.deepEqual(actualHeapSpaceNames.sort(), expectedHeapSpaces.sort()); +heapSpaceStatistics.forEach(heapSpace => { + assert.strictEqual(typeof heapSpace.space_name, 'string'); + assert.strictEqual(typeof heapSpace.space_size, 'number'); + assert.strictEqual(typeof heapSpace.space_used_size, 'number'); + assert.strictEqual(typeof heapSpace.space_available_size, 'number'); + assert.strictEqual(typeof heapSpace.physical_space_size, 'number'); +});