Skip to content

Commit dd4b668

Browse files
theanarkhIlyasShabi
authored andcommitted
v8: add heap profile API
1 parent b4387bd commit dd4b668

File tree

8 files changed

+177
-60
lines changed

8 files changed

+177
-60
lines changed

doc/api/v8.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,6 +1617,30 @@ added:
16171617
16181618
Stopping collecting the profile and the profile will be discarded.
16191619
1620+
## Class: `SyncHeapProfileHandle`
1621+
1622+
<!-- YAML
1623+
added: REPLACEME
1624+
-->
1625+
1626+
### `syncHeapProfileHandle.stop()`
1627+
1628+
<!-- YAML
1629+
added: REPLACEME
1630+
-->
1631+
1632+
* Returns: {string}
1633+
1634+
Stopping collecting the profile and return the profile data.
1635+
1636+
### `syncHeapProfileHandle[Symbol.dispose]()`
1637+
1638+
<!-- YAML
1639+
added: REPLACEME
1640+
-->
1641+
1642+
Stopping collecting the profile and the profile will be discarded.
1643+
16201644
## Class: `CPUProfileHandle`
16211645
16221646
<!-- YAML
@@ -1764,6 +1788,23 @@ const profile = handle.stop();
17641788
console.log(profile);
17651789
```
17661790
1791+
## `v8.startHeapProfile()`
1792+
1793+
<!-- YAML
1794+
added: REPLACEME
1795+
-->
1796+
1797+
* Returns: {SyncHeapProfileHandle}
1798+
1799+
Starting a heap profile then return a `SyncHeapProfileHandle` object.
1800+
This API supports `using` syntax.
1801+
1802+
```cjs
1803+
const handle = v8.startHeapProfile();
1804+
const profile = handle.stop();
1805+
console.log(profile);
1806+
```
1807+
17671808
[CppHeap]: https://v8docs.nodesource.com/node-22.4/d9/dc4/classv8_1_1_cpp_heap.html
17681809
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
17691810
[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,
@@ -191,6 +193,22 @@ class SyncCPUProfileHandle {
191193
}
192194
}
193195

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

221+
/**
222+
* Starting Heap Profile.
223+
* @returns {SyncHeapProfileHandle}
224+
*/
225+
function startHeapProfile() {
226+
_startHeapProfile();
227+
return new SyncHeapProfileHandle();
228+
}
229+
203230
/**
204231
* Return whether this string uses one byte as underlying representation or not.
205232
* @param {string} content
@@ -518,4 +545,5 @@ module.exports = {
518545
GCProfiler,
519546
isStringOneByteRepresentation,
520547
startCpuProfile,
548+
startHeapProfile,
521549
};

src/node_v8.cc

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

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

741765
SetMethod(context, target, "startCpuProfile", StartCpuProfile);
742766
SetMethod(context, target, "stopCpuProfile", StopCpuProfile);
767+
SetMethod(context, target, "startHeapProfile", StartHeapProfile);
768+
SetMethod(context, target, "stopHeapProfile", StopHeapProfile);
743769

744770
// Export symbols used by v8.isStringOneByteRepresentation()
745771
SetFastMethodNoSideEffect(context,
@@ -787,6 +813,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
787813
registry->Register(fast_is_string_one_byte_representation_);
788814
registry->Register(StartCpuProfile);
789815
registry->Register(StopCpuProfile);
816+
registry->Register(StartHeapProfile);
817+
registry->Register(StopHeapProfile);
790818
}
791819

792820
} // namespace v8_utils

src/node_worker.cc

Lines changed: 2 additions & 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;
@@ -1086,63 +1084,6 @@ void Worker::StartHeapProfile(const FunctionCallbackInfo<Value>& args) {
10861084
}
10871085
}
10881086

1089-
static void buildHeapProfileNode(Isolate* isolate,
1090-
const AllocationProfile::Node* node,
1091-
JSONWriter* writer) {
1092-
size_t selfSize = 0;
1093-
for (const auto& allocation : node->allocations)
1094-
selfSize += allocation.size * allocation.count;
1095-
1096-
writer->json_keyvalue("selfSize", selfSize);
1097-
writer->json_keyvalue("id", node->node_id);
1098-
writer->json_objectstart("callFrame");
1099-
writer->json_keyvalue("scriptId", node->script_id);
1100-
writer->json_keyvalue("lineNumber", node->line_number - 1);
1101-
writer->json_keyvalue("columnNumber", node->column_number - 1);
1102-
node::Utf8Value name(isolate, node->name);
1103-
node::Utf8Value script_name(isolate, node->script_name);
1104-
writer->json_keyvalue("functionName", *name);
1105-
writer->json_keyvalue("url", *script_name);
1106-
writer->json_objectend();
1107-
1108-
writer->json_arraystart("children");
1109-
for (const auto* child : node->children) {
1110-
writer->json_start();
1111-
buildHeapProfileNode(isolate, child, writer);
1112-
writer->json_end();
1113-
}
1114-
writer->json_arrayend();
1115-
}
1116-
1117-
static bool serializeProfile(Isolate* isolate, std::ostringstream& out_stream) {
1118-
HandleScope scope(isolate);
1119-
HeapProfiler* profiler = isolate->GetHeapProfiler();
1120-
std::unique_ptr<AllocationProfile> profile(profiler->GetAllocationProfile());
1121-
if (!profile) {
1122-
return false;
1123-
}
1124-
profiler->StopSamplingHeapProfiler();
1125-
JSONWriter writer(out_stream, true);
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-
return true;
1144-
}
1145-
11461087
void Worker::StopHeapProfile(const FunctionCallbackInfo<Value>& args) {
11471088
Worker* w;
11481089
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
@@ -1162,7 +1103,8 @@ void Worker::StopHeapProfile(const FunctionCallbackInfo<Value>& args) {
11621103
bool scheduled = w->RequestInterrupt([taker = std::move(taker),
11631104
env](Environment* worker_env) mutable {
11641105
std::ostringstream out_stream;
1165-
bool success = serializeProfile(worker_env->isolate(), out_stream);
1106+
bool success =
1107+
node::SerializeHeapProfile(worker_env->isolate(), out_stream);
11661108
env->SetImmediateThreadsafe(
11671109
[taker = std::move(taker),
11681110
out_stream = std::move(out_stream),

src/node_worker.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "json_utils.h"
99
#include "node_exit_code.h"
1010
#include "node_messaging.h"
11+
#include "util.h"
1112
#include "uv.h"
1213

1314
namespace node {

src/util.cc

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include "debug_utils-inl.h"
2828
#include "env-inl.h"
29+
#include "json_utils.h"
2930
#include "node_buffer.h"
3031
#include "node_errors.h"
3132
#include "node_internals.h"
@@ -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;
@@ -812,4 +816,61 @@ v8::Maybe<int> GetValidFileMode(Environment* env,
812816
return v8::Just(mode);
813817
}
814818

819+
static void buildHeapProfileNode(Isolate* isolate,
820+
const AllocationProfile::Node* node,
821+
JSONWriter* writer) {
822+
size_t selfSize = 0;
823+
for (const auto& allocation : node->allocations)
824+
selfSize += allocation.size * allocation.count;
825+
826+
writer->json_keyvalue("selfSize", selfSize);
827+
writer->json_keyvalue("id", node->node_id);
828+
writer->json_objectstart("callFrame");
829+
writer->json_keyvalue("scriptId", node->script_id);
830+
writer->json_keyvalue("lineNumber", node->line_number - 1);
831+
writer->json_keyvalue("columnNumber", node->column_number - 1);
832+
Utf8Value name(isolate, node->name);
833+
Utf8Value script_name(isolate, node->script_name);
834+
writer->json_keyvalue("functionName", *name);
835+
writer->json_keyvalue("url", *script_name);
836+
writer->json_objectend();
837+
838+
writer->json_arraystart("children");
839+
for (const auto* child : node->children) {
840+
writer->json_start();
841+
buildHeapProfileNode(isolate, child, writer);
842+
writer->json_end();
843+
}
844+
writer->json_arrayend();
845+
}
846+
847+
bool SerializeHeapProfile(Isolate* isolate, std::ostringstream& out_stream) {
848+
HandleScope scope(isolate);
849+
HeapProfiler* profiler = isolate->GetHeapProfiler();
850+
std::unique_ptr<AllocationProfile> profile(profiler->GetAllocationProfile());
851+
if (!profile) {
852+
return false;
853+
}
854+
JSONWriter writer(out_stream, false);
855+
writer.json_start();
856+
857+
writer.json_arraystart("samples");
858+
for (const auto& sample : profile->GetSamples()) {
859+
writer.json_start();
860+
writer.json_keyvalue("size", sample.size * sample.count);
861+
writer.json_keyvalue("nodeId", sample.node_id);
862+
writer.json_keyvalue("ordinal", static_cast<double>(sample.sample_id));
863+
writer.json_end();
864+
}
865+
writer.json_arrayend();
866+
867+
writer.json_objectstart("head");
868+
buildHeapProfileNode(isolate, profile->GetRootNode(), &writer);
869+
writer.json_objectend();
870+
871+
writer.json_end();
872+
profiler->StopSamplingHeapProfiler();
873+
return true;
874+
}
875+
815876
} // namespace node

src/util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,7 @@ inline v8::Local<v8::String> Uint32ToString(v8::Local<v8::Context> context,
10661066
->ToString(context)
10671067
.ToLocalChecked();
10681068
}
1069+
bool SerializeHeapProfile(v8::Isolate* isolate, std::ostringstream& out_stream);
10691070

10701071
} // namespace node
10711072

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

0 commit comments

Comments
 (0)