Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v8: Expose statistics about heap spaces #4463

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions doc/api/v8.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 32 additions & 1 deletion lib/v8.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,28 @@

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;
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;

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

exports.setFlagsFromString = v8binding.setFlagsFromString;

exports.getHeapSpaceStatistics = function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to just assign exports.getHeapSpaceStatistics = v8binding.getHeapSpaceStatistics;?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference: This comment is out of date and was made when the code looked this way:

exports.getHeapSpaceStatistics = function() {
  return v8binding.getHeapSpaceStatistics();
};

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;
};
12 changes: 12 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ inline Environment::~Environment() {
isolate_data()->Put();

delete[] heap_statistics_buffer_;
delete[] heap_space_statistics_buffer_;
delete[] http_parser_buffer_;
}

Expand Down Expand Up @@ -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_;
}
Expand Down
4 changes: 4 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -562,6 +565,7 @@ class Environment {
int handle_cleanup_waiting_;

uint32_t* heap_statistics_buffer_ = nullptr;
uint32_t* heap_space_statistics_buffer_ = nullptr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could in theory share the memory between these two. Not that important though.


char* http_parser_buffer_;

Expand Down
87 changes: 86 additions & 1 deletion src/node_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Value>& args) {
Environment* env = Environment::GetCurrent(args);
HeapStatistics s;
Expand All @@ -45,6 +63,23 @@ void UpdateHeapStatisticsArrayBuffer(const FunctionCallbackInfo<Value>& args) {
}


void UpdateHeapSpaceStatisticsBuffer(const FunctionCallbackInfo<Value>& 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<uint32_t>(s.name());
HEAP_SPACE_STATISTICS_PROPERTIES(V)
#undef V
}
}


void SetFlagsFromString(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Expand All @@ -62,10 +97,10 @@ void InitializeV8Bindings(Local<Object> target,
Local<Value> unused,
Local<Context> 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]);

Expand All @@ -84,6 +119,56 @@ void InitializeV8Bindings(Local<Object> 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<Array> 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<String> 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
Expand Down
19 changes: 19 additions & 0 deletions test/parallel/test-v8-stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});