Commit cf072ab0cdf for nodejs
commit cf072ab0cdf7475f8601bff043a7c933a85cebe8
Author: Ilyas Shabi <ilyasshabi94@gmail.com>
Date: Sat Sep 26 22:46:13 2026 +0200
src: expose size and count in heap profile output
Signed-off-by: ishabi <ilyasshabi94@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65737
Reviewed-By: Anna Henningsen <anna@addaleax.net>
diff --git a/doc/api/v8.md b/doc/api/v8.md
index fc69b311faa..2517866581e 100644
--- a/doc/api/v8.md
+++ b/doc/api/v8.md
@@ -1628,6 +1628,16 @@ added: v26.1.0
Stopping collecting the profile and return the profile data.
+The returned string is JSON in the Chrome DevTools sampling heap profile
+format, with a `head` call-tree and a `samples` array. On top of the DevTools
+fields, the following are added:
+
+* Each node reports `selfCount`, the number of sampled objects allocated by the
+ node (`selfSize` is the existing total in bytes).
+* Each sample reports `objectSize`, `objectCount`, and `isLive`.
+
+The DevTools `size` field stays `objectSize * objectCount` for compatibility.
+
### `syncHeapProfileHandle[Symbol.dispose]()`
<!-- YAML
diff --git a/src/node_profiling.cc b/src/node_profiling.cc
index c4725d16e81..bc595ebdbbd 100644
--- a/src/node_profiling.cc
+++ b/src/node_profiling.cc
@@ -17,10 +17,14 @@ static void BuildHeapProfileNode(Isolate* isolate,
const AllocationProfile::Node* node,
JSONWriter* writer) {
size_t selfSize = 0;
- for (const auto& allocation : node->allocations)
+ size_t selfCount = 0;
+ for (const auto& allocation : node->allocations) {
selfSize += allocation.size * allocation.count;
+ selfCount += allocation.count;
+ }
writer->json_keyvalue("selfSize", selfSize);
+ writer->json_keyvalue("selfCount", selfCount);
writer->json_keyvalue("id", node->node_id);
writer->json_objectstart("callFrame");
writer->json_keyvalue("scriptId", node->script_id);
@@ -55,8 +59,11 @@ bool SerializeHeapProfile(Isolate* isolate, std::ostringstream& out_stream) {
for (const auto& sample : profile->GetSamples()) {
writer.json_start();
writer.json_keyvalue("size", sample.size * sample.count);
+ writer.json_keyvalue("objectSize", sample.size);
+ writer.json_keyvalue("objectCount", sample.count);
writer.json_keyvalue("nodeId", sample.node_id);
writer.json_keyvalue("ordinal", static_cast<double>(sample.sample_id));
+ writer.json_keyvalue("isLive", sample.is_live);
writer.json_end();
}
writer.json_arrayend();
diff --git a/test/parallel/test-v8-heap-profile.js b/test/parallel/test-v8-heap-profile.js
index a8068b1a59c..aaaa22934b4 100644
--- a/test/parallel/test-v8-heap-profile.js
+++ b/test/parallel/test-v8-heap-profile.js
@@ -73,6 +73,31 @@ for (const value of invalidLimits) {
JSON.parse(profile);
}
+{
+ const handle = v8.startHeapProfile({ sampleInterval: 1, stackDepth: 8 });
+ const retained = [];
+ for (let i = 0; i < 4096; i++) {
+ retained.push({ i, payload: new Array(16).fill(i) });
+ }
+ const parsed = JSON.parse(handle.stop());
+
+ assert.strictEqual(typeof parsed.head.selfSize, 'number');
+ assert.strictEqual(typeof parsed.head.selfCount, 'number');
+ assert.strictEqual(typeof parsed.head.id, 'number');
+ assert.strictEqual(typeof parsed.head.callFrame, 'object');
+
+ assert(parsed.samples.length > 0);
+ for (const sample of parsed.samples) {
+ assert.strictEqual(typeof sample.size, 'number');
+ assert.strictEqual(typeof sample.objectSize, 'number');
+ assert.strictEqual(typeof sample.objectCount, 'number');
+ assert.strictEqual(typeof sample.nodeId, 'number');
+ assert.strictEqual(typeof sample.ordinal, 'number');
+ assert.strictEqual(typeof sample.isLive, 'boolean');
+ assert.strictEqual(sample.size, sample.objectSize * sample.objectCount);
+ }
+}
+
// Second stop returns undefined.
{
const handle = v8.startHeapProfile();