Skip to content

Commit 0f5a16c

Browse files
committed
src: expose cppgc::CppHeap::CollectStatistics()
Expose `CppHeap` data via `cppgc::CppHeap::CollectStatistics()` in the v8 module.
1 parent cbedcd1 commit 0f5a16c

File tree

4 files changed

+393
-0
lines changed

4 files changed

+393
-0
lines changed

doc/api/v8.md

+75
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,78 @@ buffers and external strings.
271271
}
272272
```
273273

274+
## `v8.getCppHeapStatistics(detailLevel)`
275+
276+
This method returns [CppHeap][] statistics regarding memory consumption and
277+
utilization. See V8 [`CollectStatistics()`][] API. It returns a
278+
[`HeapStatistics`][] object. You can learn more about the properties in
279+
the [V8 documentation](https://v8docs.nodesource.com/node-22.4/df/d2f/structcppgc_1_1_heap_statistics.html).
280+
281+
```js
282+
// Detailed
283+
({
284+
committed_size_bytes: 131072,
285+
resident_size_bytes: 131072,
286+
used_size_bytes: 152,
287+
space_statistics: [
288+
{
289+
name: 'NormalPageSpace0',
290+
committed_size_bytes: 0,
291+
resident_size_bytes: 0,
292+
used_size_bytes: 0,
293+
page_stats: [{}],
294+
free_list_stats: {},
295+
},
296+
{
297+
name: 'NormalPageSpace1',
298+
committed_size_bytes: 131072,
299+
resident_size_bytes: 131072,
300+
used_size_bytes: 152,
301+
page_stats: [{}],
302+
free_list_stats: {},
303+
},
304+
{
305+
name: 'NormalPageSpace2',
306+
committed_size_bytes: 0,
307+
resident_size_bytes: 0,
308+
used_size_bytes: 0,
309+
page_stats: [{}],
310+
free_list_stats: {},
311+
},
312+
{
313+
name: 'NormalPageSpace3',
314+
committed_size_bytes: 0,
315+
resident_size_bytes: 0,
316+
used_size_bytes: 0,
317+
page_stats: [{}],
318+
free_list_stats: {},
319+
},
320+
{
321+
name: 'LargePageSpace',
322+
committed_size_bytes: 0,
323+
resident_size_bytes: 0,
324+
used_size_bytes: 0,
325+
page_stats: [{}],
326+
free_list_stats: {},
327+
},
328+
],
329+
type_names: [],
330+
detail_level: 'detailed',
331+
});
332+
```
333+
334+
```js
335+
// Brief
336+
({
337+
committed_size_bytes: 131072,
338+
resident_size_bytes: 131072,
339+
used_size_bytes: 128864,
340+
space_statistics: [],
341+
type_names: [],
342+
detail_level: 'brief',
343+
});
344+
```
345+
274346
## `v8.queryObjects(ctor[, options])`
275347

276348
<!-- YAML
@@ -1304,19 +1376,22 @@ setTimeout(() => {
13041376
}, 1000);
13051377
```
13061378

1379+
[CppHeap]: https://v8docs.nodesource.com/node-22.4/d9/dc4/classv8_1_1_cpp_heap.html
13071380
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
13081381
[Hook Callbacks]: #hook-callbacks
13091382
[V8]: https://developers.google.com/v8/
13101383
[`--heapsnapshot-near-heap-limit`]: cli.md#--heapsnapshot-near-heap-limitmax_count
13111384
[`AsyncLocalStorage`]: async_context.md#class-asynclocalstorage
13121385
[`Buffer`]: buffer.md
1386+
[`CollectStatistics()`]: https://v8docs.nodesource.com/node-22.4/d9/dc4/classv8_1_1_cpp_heap.html#a3a5d09567758e608fffde50eeabc2feb
13131387
[`DefaultDeserializer`]: #class-v8defaultdeserializer
13141388
[`DefaultSerializer`]: #class-v8defaultserializer
13151389
[`Deserializer`]: #class-v8deserializer
13161390
[`ERR_BUFFER_TOO_LARGE`]: errors.md#err_buffer_too_large
13171391
[`Error`]: errors.md#class-error
13181392
[`GetHeapCodeAndMetadataStatistics`]: https://v8docs.nodesource.com/node-13.2/d5/dda/classv8_1_1_isolate.html#a6079122af17612ef54ef3348ce170866
13191393
[`GetHeapSpaceStatistics`]: https://v8docs.nodesource.com/node-13.2/d5/dda/classv8_1_1_isolate.html#ac673576f24fdc7a33378f8f57e1d13a4
1394+
[`HeapStatistics`]: https://v8docs.nodesource.com/node-22.4/d7/d51/heap-statistics_8h_source.html
13201395
[`NODE_V8_COVERAGE`]: cli.md#node_v8_coveragedir
13211396
[`Serializer`]: #class-v8serializer
13221397
[`after` callback]: #afterpromise

lib/v8.js

+19
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const {
6767
const promiseHooks = require('internal/promise_hooks');
6868
const { getOptionValue } = require('internal/options');
6969

70+
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
7071
/**
7172
* Generates a snapshot of the current V8 heap
7273
* and writes it to a JSON file.
@@ -145,6 +146,8 @@ const {
145146
heapStatisticsBuffer,
146147
heapCodeStatisticsBuffer,
147148
heapSpaceStatisticsBuffer,
149+
getCppHeapStatistics: _getCppHeapStatistics,
150+
detailLevel,
148151
} = binding;
149152

150153
const kNumberOfHeapSpaces = kHeapSpaces.length;
@@ -259,6 +262,21 @@ function setHeapSnapshotNearHeapLimit(limit) {
259262
_setHeapSnapshotNearHeapLimit(limit);
260263
}
261264

265+
const detailLevelDict = {
266+
__proto__: null,
267+
detailed: detailLevel.DETAILED,
268+
brief: detailLevel.BRIEF,
269+
};
270+
271+
function getCppHeapStatistics(type = 'detailed') {
272+
if (type !== undefined && type !== 'detailed' && type !== 'brief') {
273+
throw new ERR_INVALID_ARG_TYPE('Detail type', 'brief, detailed or undefined', type);
274+
}
275+
const result = _getCppHeapStatistics(detailLevelDict[type]);
276+
result.detail_level = type;
277+
return result;
278+
}
279+
262280
/* V8 serialization API */
263281

264282
/* JS methods for the base objects */
@@ -430,6 +448,7 @@ module.exports = {
430448
getHeapStatistics,
431449
getHeapSpaceStatistics,
432450
getHeapCodeStatistics,
451+
getCppHeapStatistics,
433452
setFlagsFromString,
434453
Serializer,
435454
Deserializer,

src/node_v8.cc

+175
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ using v8::HeapStatistics;
4242
using v8::Integer;
4343
using v8::Isolate;
4444
using v8::Local;
45+
using v8::LocalVector;
46+
using v8::Name;
4547
using v8::Object;
4648
using v8::ScriptCompiler;
4749
using v8::String;
@@ -295,6 +297,164 @@ static void SetHeapStatistics(JSONWriter* writer, Isolate* isolate) {
295297
writer->json_arrayend();
296298
}
297299

300+
static Local<Object> ConvertHeapStatsToJSObject(
301+
Isolate* isolate, const cppgc::HeapStatistics& stats) {
302+
Local<Context> context = isolate->GetCurrentContext();
303+
// Space Statistics
304+
LocalVector<Value> space_statistics_array(isolate);
305+
for (size_t i = 0; i < stats.space_stats.size(); i++) {
306+
const cppgc::HeapStatistics::SpaceStatistics& space_stats =
307+
stats.space_stats[i];
308+
// Page Statistics
309+
LocalVector<Value> page_statistics_array(isolate);
310+
for (size_t j = 0; j < space_stats.page_stats.size(); j++) {
311+
const cppgc::HeapStatistics::PageStatistics& page_stats =
312+
space_stats.page_stats[j];
313+
// Object Statistics
314+
LocalVector<Value> object_statistics_array(isolate);
315+
for (size_t k = 0; k < page_stats.object_statistics.size(); k++) {
316+
const cppgc::HeapStatistics::ObjectStatsEntry& object_stats =
317+
page_stats.object_statistics[k];
318+
Local<Name> object_stats_names[] = {
319+
FIXED_ONE_BYTE_STRING(isolate, "allocated_bytes"),
320+
FIXED_ONE_BYTE_STRING(isolate, "object_count")};
321+
Local<Value> object_stats_values[] = {
322+
Uint32::NewFromUnsigned(
323+
isolate, static_cast<uint32_t>(object_stats.allocated_bytes)),
324+
Uint32::NewFromUnsigned(
325+
isolate, static_cast<uint32_t>(object_stats.object_count))};
326+
Local<Object> object_stats_object =
327+
Object::New(isolate,
328+
Null(isolate),
329+
object_stats_names,
330+
object_stats_values,
331+
arraysize(object_stats_names));
332+
object_statistics_array.emplace_back(object_stats_object);
333+
}
334+
335+
// Set page statistics
336+
Local<Name> page_stats_names[] = {
337+
FIXED_ONE_BYTE_STRING(isolate, "committed_size_bytes"),
338+
FIXED_ONE_BYTE_STRING(isolate, "resident_size_bytes"),
339+
FIXED_ONE_BYTE_STRING(isolate, "used_size_bytes"),
340+
FIXED_ONE_BYTE_STRING(isolate, "object_statistics")};
341+
Local<Value> page_stats_values[] = {
342+
Uint32::NewFromUnsigned(
343+
isolate, static_cast<uint32_t>(page_stats.committed_size_bytes)),
344+
Uint32::NewFromUnsigned(
345+
isolate, static_cast<uint32_t>(page_stats.resident_size_bytes)),
346+
Uint32::NewFromUnsigned(
347+
isolate, static_cast<uint32_t>(page_stats.used_size_bytes)),
348+
Array::New(isolate,
349+
object_statistics_array.data(),
350+
object_statistics_array.size())};
351+
Local<Object> page_stats_object =
352+
Object::New(isolate,
353+
Null(isolate),
354+
page_stats_names,
355+
page_stats_values,
356+
arraysize(page_stats_names));
357+
page_statistics_array.emplace_back(page_stats_object);
358+
}
359+
360+
// Free List Statistics
361+
Local<Name> free_list_statistics_names[] = {
362+
FIXED_ONE_BYTE_STRING(isolate, "bucket_size"),
363+
FIXED_ONE_BYTE_STRING(isolate, "free_count"),
364+
FIXED_ONE_BYTE_STRING(isolate, "free_size")};
365+
Local<Value> free_list_statistics_values[] = {
366+
ToV8Value(context, space_stats.free_list_stats.bucket_size, isolate)
367+
.ToLocalChecked(),
368+
ToV8Value(context, space_stats.free_list_stats.free_count, isolate)
369+
.ToLocalChecked(),
370+
ToV8Value(context, space_stats.free_list_stats.free_size, isolate)
371+
.ToLocalChecked()};
372+
Local<Object> free_list_statistics_obj =
373+
Object::New(isolate,
374+
Null(isolate),
375+
free_list_statistics_names,
376+
free_list_statistics_values,
377+
arraysize(free_list_statistics_names));
378+
379+
// Set Space Statistics
380+
Local<Name> space_stats_names[] = {
381+
FIXED_ONE_BYTE_STRING(isolate, "name"),
382+
FIXED_ONE_BYTE_STRING(isolate, "committed_size_bytes"),
383+
FIXED_ONE_BYTE_STRING(isolate, "resident_size_bytes"),
384+
FIXED_ONE_BYTE_STRING(isolate, "used_size_bytes"),
385+
FIXED_ONE_BYTE_STRING(isolate, "page_stats"),
386+
FIXED_ONE_BYTE_STRING(isolate, "free_list_stats")};
387+
Local<Value> space_stats_values[] = {
388+
ToV8Value(context, stats.space_stats[i].name.c_str(), isolate)
389+
.ToLocalChecked(),
390+
Uint32::NewFromUnsigned(
391+
isolate,
392+
static_cast<uint32_t>(stats.space_stats[i].committed_size_bytes)),
393+
Uint32::NewFromUnsigned(
394+
isolate,
395+
static_cast<uint32_t>(stats.space_stats[i].resident_size_bytes)),
396+
Uint32::NewFromUnsigned(
397+
isolate,
398+
static_cast<uint32_t>(stats.space_stats[i].used_size_bytes)),
399+
Array::New(isolate,
400+
page_statistics_array.data(),
401+
page_statistics_array.size()),
402+
free_list_statistics_obj,
403+
};
404+
Local<Object> space_stats_object =
405+
Object::New(isolate,
406+
Null(isolate),
407+
space_stats_names,
408+
space_stats_values,
409+
arraysize(space_stats_names));
410+
space_statistics_array.emplace_back(space_stats_object);
411+
}
412+
413+
// Set heap statistics
414+
Local<Name> heap_statistics_names[] = {
415+
FIXED_ONE_BYTE_STRING(isolate, "committed_size_bytes"),
416+
FIXED_ONE_BYTE_STRING(isolate, "resident_size_bytes"),
417+
FIXED_ONE_BYTE_STRING(isolate, "used_size_bytes"),
418+
FIXED_ONE_BYTE_STRING(isolate, "space_statistics"),
419+
FIXED_ONE_BYTE_STRING(isolate, "type_names")};
420+
421+
Local<Value> heap_statistics_values[] = {
422+
Uint32::NewFromUnsigned(
423+
isolate, static_cast<uint32_t>(stats.committed_size_bytes)),
424+
Uint32::NewFromUnsigned(isolate,
425+
static_cast<uint32_t>(stats.resident_size_bytes)),
426+
Uint32::NewFromUnsigned(isolate,
427+
static_cast<uint32_t>(stats.used_size_bytes)),
428+
Array::New(isolate,
429+
space_statistics_array.data(),
430+
space_statistics_array.size()),
431+
ToV8Value(context, stats.type_names, isolate).ToLocalChecked()};
432+
433+
Local<Object> heap_statistics_object =
434+
Object::New(isolate,
435+
Null(isolate),
436+
heap_statistics_names,
437+
heap_statistics_values,
438+
arraysize(heap_statistics_names));
439+
440+
return heap_statistics_object;
441+
}
442+
443+
static void getCppHeapStatistics(const FunctionCallbackInfo<Value>& args) {
444+
Isolate* isolate = args.GetIsolate();
445+
HandleScope handle_scope(isolate);
446+
447+
CHECK_EQ(args.Length(), 1);
448+
CHECK(args[0]->IsInt32());
449+
450+
cppgc::HeapStatistics stats = isolate->GetCppHeap()->CollectStatistics(
451+
static_cast<cppgc::HeapStatistics::DetailLevel>(
452+
args[0].As<v8::Int32>()->Value()));
453+
454+
Local<Object> result = ConvertHeapStatsToJSObject(isolate, stats);
455+
args.GetReturnValue().Set(result);
456+
}
457+
298458
static void BeforeGCCallback(Isolate* isolate,
299459
v8::GCType gc_type,
300460
v8::GCCallbackFlags flags,
@@ -440,6 +600,8 @@ void Initialize(Local<Object> target,
440600
target,
441601
"updateHeapCodeStatisticsBuffer",
442602
UpdateHeapCodeStatisticsBuffer);
603+
SetMethod(
604+
context, target, "getCppHeapStatistics", getCppHeapStatistics);
443605

444606
size_t number_of_heap_spaces = env->isolate()->NumberOfHeapSpaces();
445607

@@ -486,6 +648,18 @@ void Initialize(Local<Object> target,
486648
SetProtoMethod(env->isolate(), t, "start", GCProfiler::Start);
487649
SetProtoMethod(env->isolate(), t, "stop", GCProfiler::Stop);
488650
SetConstructorFunction(context, target, "GCProfiler", t);
651+
652+
{
653+
Isolate* isolate = env->isolate();
654+
Local<Object> detail_level = Object::New(isolate);
655+
cppgc::HeapStatistics::DetailLevel DETAILED =
656+
cppgc::HeapStatistics::DetailLevel::kDetailed;
657+
cppgc::HeapStatistics::DetailLevel BRIEF =
658+
cppgc::HeapStatistics::DetailLevel::kBrief;
659+
NODE_DEFINE_CONSTANT(detail_level, DETAILED);
660+
NODE_DEFINE_CONSTANT(detail_level, BRIEF);
661+
READONLY_PROPERTY(target, "detailLevel", detail_level);
662+
}
489663
}
490664

491665
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
@@ -498,6 +672,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
498672
registry->Register(GCProfiler::New);
499673
registry->Register(GCProfiler::Start);
500674
registry->Register(GCProfiler::Stop);
675+
registry->Register(getCppHeapStatistics);
501676
}
502677

503678
} // namespace v8_utils

0 commit comments

Comments
 (0)