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