Skip to content

Commit 70788c9

Browse files
committed
v8: add heap profile API
1 parent f9fcc74 commit 70788c9

File tree

9 files changed

+181
-60
lines changed

9 files changed

+181
-60
lines changed

doc/api/v8.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,30 @@ added: REPLACEME
14221422

14231423
Stopping collecting the profile and the profile will be discarded.
14241424

1425+
## Class: `SyncHeapProfileHandle`
1426+
1427+
<!-- YAML
1428+
added: REPLACEME
1429+
-->
1430+
1431+
### `syncHeapProfileHandle.stop()`
1432+
1433+
<!-- YAML
1434+
added: REPLACEME
1435+
-->
1436+
1437+
* Returns: {string}
1438+
1439+
Stopping collecting the profile and return the profile data.
1440+
1441+
### `syncHeapProfileHandle[Symbol.dispose]()`
1442+
1443+
<!-- YAML
1444+
added: REPLACEME
1445+
-->
1446+
1447+
Stopping collecting the profile and the profile will be discarded.
1448+
14251449
## Class: `CPUProfileHandle`
14261450

14271451
<!-- YAML
@@ -1534,6 +1558,23 @@ const profile = handle.stop();
15341558
console.log(profile);
15351559
```
15361560

1561+
## `v8.startHeapProfile()`
1562+
1563+
<!-- YAML
1564+
added: REPLACEME
1565+
-->
1566+
1567+
* Returns: {SyncHeapProfileHandle}
1568+
1569+
Starting a heap profile then return a `SyncHeapProfileHandle` object.
1570+
This API supports `using` syntax.
1571+
1572+
```cjs
1573+
const handle = v8.startHeapProfile();
1574+
const profile = handle.stop();
1575+
console.log(profile);
1576+
```
1577+
15371578
[CppHeap]: https://v8docs.nodesource.com/node-22.4/d9/dc4/classv8_1_1_cpp_heap.html
15381579
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
15391580
[Hook Callbacks]: #hook-callbacks

lib/v8.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ const {
115115
setFlagsFromString: _setFlagsFromString,
116116
startCpuProfile: _startCpuProfile,
117117
stopCpuProfile: _stopCpuProfile,
118+
startHeapProfile: _startHeapProfile,
119+
stopHeapProfile: _stopHeapProfile,
118120
isStringOneByteRepresentation: _isStringOneByteRepresentation,
119121
updateHeapStatisticsBuffer,
120122
updateHeapSpaceStatisticsBuffer,
@@ -190,6 +192,22 @@ class SyncCPUProfileHandle {
190192
}
191193
}
192194

195+
class SyncHeapProfileHandle {
196+
#stopped = false;
197+
198+
stop() {
199+
if (this.#stopped) {
200+
return;
201+
}
202+
this.#stopped = true;
203+
return _stopHeapProfile();
204+
};
205+
206+
[SymbolDispose]() {
207+
this.stop();
208+
}
209+
}
210+
193211
/**
194212
* Starting CPU Profile.
195213
* @returns {SyncCPUProfileHandle}
@@ -199,6 +217,15 @@ function startCpuProfile() {
199217
return new SyncCPUProfileHandle(id);
200218
}
201219

220+
/**
221+
* Starting Heap Profile.
222+
* @returns {SyncHeapProfileHandle}
223+
*/
224+
function startHeapProfile() {
225+
_startHeapProfile();
226+
return new SyncHeapProfileHandle();
227+
}
228+
202229
/**
203230
* Return whether this string uses one byte as underlying representation or not.
204231
* @param {string} content
@@ -512,4 +539,5 @@ module.exports = {
512539
GCProfiler,
513540
isStringOneByteRepresentation,
514541
startCpuProfile,
542+
startHeapProfile,
515543
};

src/node_v8.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,32 @@ void StopCpuProfile(const FunctionCallbackInfo<Value>& args) {
279279
}
280280
}
281281

282+
void StartHeapProfile(const FunctionCallbackInfo<Value>& args) {
283+
Isolate* isolate = args.GetIsolate();
284+
if (isolate->GetHeapProfiler()->StartSamplingHeapProfiler()) {
285+
return;
286+
}
287+
THROW_ERR_HEAP_PROFILE_HAVE_BEEN_STARTED(isolate,
288+
"Heap profile has been started");
289+
}
290+
291+
void StopHeapProfile(const FunctionCallbackInfo<Value>& args) {
292+
Environment* env = Environment::GetCurrent(args);
293+
Isolate* isolate = env->isolate();
294+
std::ostringstream out_stream;
295+
bool success = node::SerializeHeapProfile(isolate, out_stream);
296+
if (success) {
297+
Local<Value> result;
298+
if (ToV8Value(env->context(), out_stream.str(), isolate)
299+
.ToLocal(&result)) {
300+
args.GetReturnValue().Set(result);
301+
}
302+
} else {
303+
THROW_ERR_HEAP_PROFILE_NOT_STARTED(isolate,
304+
"heap profile not started");
305+
}
306+
}
307+
282308
static void IsStringOneByteRepresentation(
283309
const FunctionCallbackInfo<Value>& args) {
284310
CHECK_EQ(args.Length(), 1);
@@ -739,6 +765,8 @@ void Initialize(Local<Object> target,
739765

740766
SetMethod(context, target, "startCpuProfile", StartCpuProfile);
741767
SetMethod(context, target, "stopCpuProfile", StopCpuProfile);
768+
SetMethod(context, target, "startHeapProfile", StartHeapProfile);
769+
SetMethod(context, target, "stopHeapProfile", StopHeapProfile);
742770

743771
// Export symbols used by v8.isStringOneByteRepresentation()
744772
SetFastMethodNoSideEffect(context,
@@ -786,6 +814,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
786814
registry->Register(fast_is_string_one_byte_representation_);
787815
registry->Register(StartCpuProfile);
788816
registry->Register(StopCpuProfile);
817+
registry->Register(StartHeapProfile);
818+
registry->Register(StopHeapProfile);
789819
}
790820

791821
} // namespace v8_utils

src/node_worker.cc

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
using node::kAllowedInEnvvar;
2222
using node::kDisallowedInEnvvar;
23-
using v8::AllocationProfile;
2423
using v8::Array;
2524
using v8::ArrayBuffer;
2625
using v8::Boolean;
@@ -33,7 +32,6 @@ using v8::Float64Array;
3332
using v8::FunctionCallbackInfo;
3433
using v8::FunctionTemplate;
3534
using v8::HandleScope;
36-
using v8::HeapProfiler;
3735
using v8::HeapStatistics;
3836
using v8::Integer;
3937
using v8::Isolate;
@@ -1087,63 +1085,6 @@ void Worker::StartHeapProfile(const FunctionCallbackInfo<Value>& args) {
10871085
}
10881086
}
10891087

1090-
static void buildHeapProfileNode(Isolate* isolate,
1091-
const AllocationProfile::Node* node,
1092-
JSONWriter* writer) {
1093-
size_t selfSize = 0;
1094-
for (const auto& allocation : node->allocations)
1095-
selfSize += allocation.size * allocation.count;
1096-
1097-
writer->json_keyvalue("selfSize", selfSize);
1098-
writer->json_keyvalue("id", node->node_id);
1099-
writer->json_objectstart("callFrame");
1100-
writer->json_keyvalue("scriptId", node->script_id);
1101-
writer->json_keyvalue("lineNumber", node->line_number - 1);
1102-
writer->json_keyvalue("columnNumber", node->column_number - 1);
1103-
node::Utf8Value name(isolate, node->name);
1104-
node::Utf8Value script_name(isolate, node->script_name);
1105-
writer->json_keyvalue("functionName", *name);
1106-
writer->json_keyvalue("url", *script_name);
1107-
writer->json_objectend();
1108-
1109-
writer->json_arraystart("children");
1110-
for (const auto* child : node->children) {
1111-
writer->json_start();
1112-
buildHeapProfileNode(isolate, child, writer);
1113-
writer->json_end();
1114-
}
1115-
writer->json_arrayend();
1116-
}
1117-
1118-
static bool serializeProfile(Isolate* isolate, std::ostringstream& out_stream) {
1119-
HandleScope scope(isolate);
1120-
HeapProfiler* profiler = isolate->GetHeapProfiler();
1121-
std::unique_ptr<AllocationProfile> profile(profiler->GetAllocationProfile());
1122-
if (!profile) {
1123-
return false;
1124-
}
1125-
JSONWriter writer(out_stream, false);
1126-
writer.json_start();
1127-
1128-
writer.json_arraystart("samples");
1129-
for (const auto& sample : profile->GetSamples()) {
1130-
writer.json_start();
1131-
writer.json_keyvalue("size", sample.size * sample.count);
1132-
writer.json_keyvalue("nodeId", sample.node_id);
1133-
writer.json_keyvalue("ordinal", static_cast<double>(sample.sample_id));
1134-
writer.json_end();
1135-
}
1136-
writer.json_arrayend();
1137-
1138-
writer.json_objectstart("head");
1139-
buildHeapProfileNode(isolate, profile->GetRootNode(), &writer);
1140-
writer.json_objectend();
1141-
1142-
writer.json_end();
1143-
profiler->StopSamplingHeapProfiler();
1144-
return true;
1145-
}
1146-
11471088
void Worker::StopHeapProfile(const FunctionCallbackInfo<Value>& args) {
11481089
Worker* w;
11491090
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
@@ -1163,7 +1104,7 @@ void Worker::StopHeapProfile(const FunctionCallbackInfo<Value>& args) {
11631104
bool scheduled = w->RequestInterrupt([taker = std::move(taker),
11641105
env](Environment* worker_env) mutable {
11651106
std::ostringstream out_stream;
1166-
bool success = serializeProfile(worker_env->isolate(), out_stream);
1107+
bool success = node::SerializeHeapProfile(worker_env->isolate(), out_stream);
11671108
env->SetImmediateThreadsafe(
11681109
[taker = std::move(taker),
11691110
out_stream = std::move(out_stream),

src/node_worker.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "node_exit_code.h"
1010
#include "node_messaging.h"
1111
#include "uv.h"
12+
#include "util.h"
1213

1314
namespace node {
1415

src/util.cc

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "node_v8_platform-inl.h"
3434
#include "string_bytes.h"
3535
#include "v8-value.h"
36+
#include "json_utils.h"
3637

3738
#ifdef _WIN32
3839
#include <io.h> // _S_IREAD _S_IWRITE
@@ -85,10 +86,13 @@ constexpr int kMaximumCopyMode =
8586

8687
namespace node {
8788

89+
using v8::AllocationProfile;
8890
using v8::ArrayBuffer;
8991
using v8::ArrayBufferView;
9092
using v8::Context;
9193
using v8::FunctionTemplate;
94+
using v8::HandleScope;
95+
using v8::HeapProfiler;
9296
using v8::Isolate;
9397
using v8::Local;
9498
using v8::Object;
@@ -902,4 +906,62 @@ v8::Maybe<int> GetValidFileMode(Environment* env,
902906
return v8::Just(mode);
903907
}
904908

909+
910+
static void buildHeapProfileNode(Isolate* isolate,
911+
const AllocationProfile::Node* node,
912+
JSONWriter* writer) {
913+
size_t selfSize = 0;
914+
for (const auto& allocation : node->allocations)
915+
selfSize += allocation.size * allocation.count;
916+
917+
writer->json_keyvalue("selfSize", selfSize);
918+
writer->json_keyvalue("id", node->node_id);
919+
writer->json_objectstart("callFrame");
920+
writer->json_keyvalue("scriptId", node->script_id);
921+
writer->json_keyvalue("lineNumber", node->line_number - 1);
922+
writer->json_keyvalue("columnNumber", node->column_number - 1);
923+
Utf8Value name(isolate, node->name);
924+
Utf8Value script_name(isolate, node->script_name);
925+
writer->json_keyvalue("functionName", *name);
926+
writer->json_keyvalue("url", *script_name);
927+
writer->json_objectend();
928+
929+
writer->json_arraystart("children");
930+
for (const auto* child : node->children) {
931+
writer->json_start();
932+
buildHeapProfileNode(isolate, child, writer);
933+
writer->json_end();
934+
}
935+
writer->json_arrayend();
936+
}
937+
938+
bool SerializeHeapProfile(Isolate* isolate, std::ostringstream& out_stream) {
939+
HandleScope scope(isolate);
940+
HeapProfiler* profiler = isolate->GetHeapProfiler();
941+
std::unique_ptr<AllocationProfile> profile(profiler->GetAllocationProfile());
942+
if (!profile) {
943+
return false;
944+
}
945+
JSONWriter writer(out_stream, false);
946+
writer.json_start();
947+
948+
writer.json_arraystart("samples");
949+
for (const auto& sample : profile->GetSamples()) {
950+
writer.json_start();
951+
writer.json_keyvalue("size", sample.size * sample.count);
952+
writer.json_keyvalue("nodeId", sample.node_id);
953+
writer.json_keyvalue("ordinal", static_cast<double>(sample.sample_id));
954+
writer.json_end();
955+
}
956+
writer.json_arrayend();
957+
958+
writer.json_objectstart("head");
959+
buildHeapProfileNode(isolate, profile->GetRootNode(), &writer);
960+
writer.json_objectend();
961+
962+
writer.json_end();
963+
profiler->StopSamplingHeapProfiler();
964+
return true;
965+
}
966+
905967
} // namespace node

src/util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,8 @@ inline v8::MaybeLocal<v8::Object> NewDictionaryInstanceNullProto(
10551055
v8::Local<v8::DictionaryTemplate> tmpl,
10561056
v8::MemorySpan<v8::MaybeLocal<v8::Value>> property_values);
10571057

1058+
bool SerializeHeapProfile(v8::Isolate* isolate, std::ostringstream& out_stream);
1059+
10581060
} // namespace node
10591061

10601062
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const v8 = require('v8');
6+
7+
const handle = v8.startHeapProfile();
8+
try {
9+
v8.startHeapProfile();
10+
} catch (err) {
11+
assert.strictEqual(err.code, 'ERR_HEAP_PROFILE_HAVE_BEEN_STARTED');
12+
}
13+
const profile = handle.stop();
14+
assert.ok(typeof profile === 'string');
15+
assert.ok(profile.length > 0);

tools/doc/type-parser.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ const customTypesMap = {
353353
'CPUProfileHandle': 'v8.html#class-cpuprofilehandle',
354354
'HeapProfileHandle': 'v8.html#class-heapprofilehandle',
355355
'SyncCPUProfileHandle': 'v8.html#class-synccpuprofilehandle',
356+
'SyncHeapProfileHandle': 'v8.html#class-syncheapprofilehandle',
356357
};
357358

358359
const arrayPart = /(?:\[])+$/;

0 commit comments

Comments
 (0)