Skip to content

Commit e78d71a

Browse files
committed
v8: add cpu profile APIs
1 parent 961554c commit e78d71a

File tree

6 files changed

+129
-1
lines changed

6 files changed

+129
-1
lines changed

doc/api/v8.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,6 +1398,30 @@ setTimeout(() => {
13981398
}, 1000);
13991399
```
14001400

1401+
## Class: `SyncCPUProfileHandle`
1402+
1403+
<!-- YAML
1404+
added: REPLACEME
1405+
-->
1406+
1407+
### `syncCpuProfileHandle.stop()`
1408+
1409+
<!-- YAML
1410+
added: REPLACEME
1411+
-->
1412+
1413+
* Returns: {string}
1414+
1415+
Stopping collecting the profile and return the profile data.
1416+
1417+
### `syncCpuProfileHandle[Symbol.dispose]()`
1418+
1419+
<!-- YAML
1420+
added: REPLACEME
1421+
-->
1422+
1423+
Stopping collecting the profile and the profile will be discarded.
1424+
14011425
## Class: `CPUProfileHandle`
14021426

14031427
<!-- YAML
@@ -1466,6 +1490,23 @@ writeString('hello');
14661490
writeString('你好');
14671491
```
14681492

1493+
## `v8.startCpuProfile()`
1494+
1495+
<!-- YAML
1496+
added: REPLACEME
1497+
-->
1498+
1499+
* Returns: {SyncCPUProfileHandle}
1500+
1501+
Starting a CPU profile then return a `SyncCPUProfileHandle` object.
1502+
This API supports `using` syntax.
1503+
1504+
```cjs
1505+
const handle = v8.startCpuProfile();
1506+
const profile = handle.stop();
1507+
console.log(profile);
1508+
```
1509+
14691510
[CppHeap]: https://v8docs.nodesource.com/node-22.4/d9/dc4/classv8_1_1_cpp_heap.html
14701511
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
14711512
[Hook Callbacks]: #hook-callbacks

lib/v8.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const {
2727
Int8Array,
2828
JSONParse,
2929
ObjectPrototypeToString,
30+
SymbolDispose,
3031
Uint16Array,
3132
Uint32Array,
3233
Uint8Array,
@@ -112,6 +113,8 @@ const binding = internalBinding('v8');
112113
const {
113114
cachedDataVersionTag,
114115
setFlagsFromString: _setFlagsFromString,
116+
startCpuProfile: _startCpuProfile,
117+
stopCpuProfile: _stopCpuProfile,
115118
isStringOneByteRepresentation: _isStringOneByteRepresentation,
116119
updateHeapStatisticsBuffer,
117120
updateHeapSpaceStatisticsBuffer,
@@ -166,6 +169,36 @@ function setFlagsFromString(flags) {
166169
_setFlagsFromString(flags);
167170
}
168171

172+
class SyncCPUProfileHandle {
173+
#id = null;
174+
#stopped = false;
175+
176+
constructor(id) {
177+
this.#id = id;
178+
}
179+
180+
stop() {
181+
if (this.#stopped) {
182+
return;
183+
}
184+
this.#stopped = true;
185+
return _stopCpuProfile(this.#id);
186+
};
187+
188+
[SymbolDispose]() {
189+
this.stop();
190+
}
191+
}
192+
193+
/**
194+
* Starting CPU Profile.
195+
* @returns {SyncCPUProfileHandle}
196+
*/
197+
function startCpuProfile() {
198+
const id = _startCpuProfile();
199+
return new SyncCPUProfileHandle(id);
200+
}
201+
169202
/**
170203
* Return whether this string uses one byte as underlying representation or not.
171204
* @param {string} content
@@ -478,4 +511,5 @@ module.exports = {
478511
setHeapSnapshotNearHeapLimit,
479512
GCProfiler,
480513
isStringOneByteRepresentation,
514+
startCpuProfile,
481515
};

src/node_v8.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,18 @@
2828
#include "node_external_reference.h"
2929
#include "util-inl.h"
3030
#include "v8.h"
31+
#include "v8-profiler.h"
3132

3233
namespace node {
3334
namespace v8_utils {
3435
using v8::Array;
3536
using v8::BigInt;
3637
using v8::CFunction;
3738
using v8::Context;
39+
using v8::CpuProfile;
40+
using v8::CpuProfiler;
41+
using v8::CpuProfilingResult;
42+
using v8::CpuProfilingStatus;
3843
using v8::FunctionCallbackInfo;
3944
using v8::FunctionTemplate;
4045
using v8::HandleScope;
@@ -47,6 +52,8 @@ using v8::Local;
4752
using v8::LocalVector;
4853
using v8::MaybeLocal;
4954
using v8::Name;
55+
using v8::NewStringType;
56+
using v8::Number;
5057
using v8::Object;
5158
using v8::ScriptCompiler;
5259
using v8::String;
@@ -243,6 +250,36 @@ void SetFlagsFromString(const FunctionCallbackInfo<Value>& args) {
243250
V8::SetFlagsFromString(*flags, static_cast<size_t>(flags.length()));
244251
}
245252

253+
void StartCpuProfile(const FunctionCallbackInfo<Value>& args) {
254+
Environment* env = Environment::GetCurrent(args);
255+
Isolate* isolate = env->isolate();
256+
CpuProfilingResult result = env->StartCpuProfile();
257+
if (result.status == CpuProfilingStatus::kErrorTooManyProfilers) {
258+
return THROW_ERR_CPU_PROFILE_TOO_MANY(isolate, "There are too many CPU profiles");
259+
} else if (result.status == CpuProfilingStatus::kStarted) {
260+
args.GetReturnValue().Set(Number::New(isolate, result.id));
261+
}
262+
}
263+
264+
void StopCpuProfile(const FunctionCallbackInfo<Value>& args) {
265+
Environment* env = Environment::GetCurrent(args);
266+
Isolate* isolate = env->isolate();
267+
CHECK(args[0]->IsUint32());
268+
uint32_t profile_id = args[0]->Uint32Value(env->context()).FromJust();
269+
CpuProfile* profile = env->StopCpuProfile(profile_id);
270+
if (!profile) {
271+
return THROW_ERR_CPU_PROFILE_NOT_STARTED(isolate, "CPU profile not started");
272+
}
273+
auto json_out_stream = std::make_unique<node::JSONOutputStream>();
274+
profile->Serialize(json_out_stream.get(),
275+
CpuProfile::SerializationFormat::kJSON);
276+
profile->Delete();
277+
Local<Value> ret;
278+
if (ToV8Value(env->context(), json_out_stream->out_stream().str(), isolate).ToLocal(&ret)) {
279+
args.GetReturnValue().Set(ret);
280+
}
281+
}
282+
246283
static void IsStringOneByteRepresentation(
247284
const FunctionCallbackInfo<Value>& args) {
248285
CHECK_EQ(args.Length(), 1);
@@ -682,6 +719,9 @@ void Initialize(Local<Object> target,
682719
// Export symbols used by v8.setFlagsFromString()
683720
SetMethod(context, target, "setFlagsFromString", SetFlagsFromString);
684721

722+
SetMethod(context, target, "startCpuProfile", StartCpuProfile);
723+
SetMethod(context, target, "stopCpuProfile", StopCpuProfile);
724+
685725
// Export symbols used by v8.isStringOneByteRepresentation()
686726
SetFastMethodNoSideEffect(context,
687727
target,
@@ -726,6 +766,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
726766
registry->Register(GetCppHeapStatistics);
727767
registry->Register(IsStringOneByteRepresentation);
728768
registry->Register(fast_is_string_one_byte_representation_);
769+
registry->Register(StartCpuProfile);
770+
registry->Register(StopCpuProfile);
729771
}
730772

731773
} // namespace v8_utils

src/node_v8.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include "node_snapshotable.h"
1111
#include "util.h"
1212
#include "v8.h"
13-
13+
#include "node_errors.h"
1414
namespace node {
1515
class Environment;
1616
struct InternalFieldInfoBase;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const v8 = require('v8');
6+
7+
const handle = v8.startCpuProfile();
8+
JSON.parse(handle.stop());
9+
// Call stop() again
10+
assert.ok(handle.stop() === undefined);

tools/doc/type-parser.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ const customTypesMap = {
345345
'LockManager': 'worker_threads.html#class-lockmanager',
346346
'LockManagerSnapshot': 'https://developer.mozilla.org/en-US/docs/Web/API/LockManagerSnapshot',
347347
'CPUProfileHandle': 'v8.html#class-cpuprofilehandle',
348+
'SyncCPUProfileHandle': 'v8.html#class-synccpuprofilehandle',
348349
};
349350

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

0 commit comments

Comments
 (0)