diff --git a/doc/api/v8.md b/doc/api/v8.md index 9006e76984..ff4b5614f5 100644 --- a/doc/api/v8.md +++ b/doc/api/v8.md @@ -1398,6 +1398,30 @@ setTimeout(() => { }, 1000); ``` +## Class: `SyncCPUProfileHandle` + + + +### `syncCpuProfileHandle.stop()` + + + +* Returns: {string} + +Stopping collecting the profile and return the profile data. + +### `syncCpuProfileHandle[Symbol.dispose]()` + + + +Stopping collecting the profile and the profile will be discarded. + ## Class: `CPUProfileHandle` + +* Returns: {SyncCPUProfileHandle} + +Starting a CPU profile then return a `SyncCPUProfileHandle` object. +This API supports `using` syntax. + +```cjs +const handle = v8.startCpuProfile(); +const profile = handle.stop(); +console.log(profile); +``` + [CppHeap]: https://v8docs.nodesource.com/node-22.4/d9/dc4/classv8_1_1_cpp_heap.html [HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm [Hook Callbacks]: #hook-callbacks diff --git a/lib/v8.js b/lib/v8.js index d60ae0bc41..47f6941037 100644 --- a/lib/v8.js +++ b/lib/v8.js @@ -27,6 +27,7 @@ const { Int8Array, JSONParse, ObjectPrototypeToString, + SymbolDispose, Uint16Array, Uint32Array, Uint8Array, @@ -112,6 +113,8 @@ const binding = internalBinding('v8'); const { cachedDataVersionTag, setFlagsFromString: _setFlagsFromString, + startCpuProfile: _startCpuProfile, + stopCpuProfile: _stopCpuProfile, isStringOneByteRepresentation: _isStringOneByteRepresentation, updateHeapStatisticsBuffer, updateHeapSpaceStatisticsBuffer, @@ -166,6 +169,36 @@ function setFlagsFromString(flags) { _setFlagsFromString(flags); } +class SyncCPUProfileHandle { + #id = null; + #stopped = false; + + constructor(id) { + this.#id = id; + } + + stop() { + if (this.#stopped) { + return; + } + this.#stopped = true; + return _stopCpuProfile(this.#id); + }; + + [SymbolDispose]() { + this.stop(); + } +} + +/** + * Starting CPU Profile. + * @returns {SyncCPUProfileHandle} + */ +function startCpuProfile() { + const id = _startCpuProfile(); + return new SyncCPUProfileHandle(id); +} + /** * Return whether this string uses one byte as underlying representation or not. * @param {string} content @@ -478,4 +511,5 @@ module.exports = { setHeapSnapshotNearHeapLimit, GCProfiler, isStringOneByteRepresentation, + startCpuProfile, }; diff --git a/src/node_v8.cc b/src/node_v8.cc index 152b030198..ce253d7fa0 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -27,6 +27,7 @@ #include "node.h" #include "node_external_reference.h" #include "util-inl.h" +#include "v8-profiler.h" #include "v8.h" namespace node { @@ -35,6 +36,9 @@ using v8::Array; using v8::BigInt; using v8::CFunction; using v8::Context; +using v8::CpuProfile; +using v8::CpuProfilingResult; +using v8::CpuProfilingStatus; using v8::DictionaryTemplate; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; @@ -47,6 +51,7 @@ using v8::Isolate; using v8::Local; using v8::LocalVector; using v8::MaybeLocal; +using v8::Number; using v8::Object; using v8::ScriptCompiler; using v8::String; @@ -241,6 +246,39 @@ void SetFlagsFromString(const FunctionCallbackInfo& args) { V8::SetFlagsFromString(*flags, static_cast(flags.length())); } +void StartCpuProfile(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + CpuProfilingResult result = env->StartCpuProfile(); + if (result.status == CpuProfilingStatus::kErrorTooManyProfilers) { + return THROW_ERR_CPU_PROFILE_TOO_MANY(isolate, + "There are too many CPU profiles"); + } else if (result.status == CpuProfilingStatus::kStarted) { + args.GetReturnValue().Set(Number::New(isolate, result.id)); + } +} + +void StopCpuProfile(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + CHECK(args[0]->IsUint32()); + uint32_t profile_id = args[0]->Uint32Value(env->context()).FromJust(); + CpuProfile* profile = env->StopCpuProfile(profile_id); + if (!profile) { + return THROW_ERR_CPU_PROFILE_NOT_STARTED(isolate, + "CPU profile not started"); + } + auto json_out_stream = std::make_unique(); + profile->Serialize(json_out_stream.get(), + CpuProfile::SerializationFormat::kJSON); + profile->Delete(); + Local ret; + if (ToV8Value(env->context(), json_out_stream->out_stream().str(), isolate) + .ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } +} + static void IsStringOneByteRepresentation( const FunctionCallbackInfo& args) { CHECK_EQ(args.Length(), 1); @@ -699,6 +737,9 @@ void Initialize(Local target, // Export symbols used by v8.setFlagsFromString() SetMethod(context, target, "setFlagsFromString", SetFlagsFromString); + SetMethod(context, target, "startCpuProfile", StartCpuProfile); + SetMethod(context, target, "stopCpuProfile", StopCpuProfile); + // Export symbols used by v8.isStringOneByteRepresentation() SetFastMethodNoSideEffect(context, target, @@ -743,6 +784,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetCppHeapStatistics); registry->Register(IsStringOneByteRepresentation); registry->Register(fast_is_string_one_byte_representation_); + registry->Register(StartCpuProfile); + registry->Register(StopCpuProfile); } } // namespace v8_utils diff --git a/src/node_v8.h b/src/node_v8.h index d3797432a2..581972b13d 100644 --- a/src/node_v8.h +++ b/src/node_v8.h @@ -7,6 +7,7 @@ #include "aliased_buffer.h" #include "base_object.h" #include "json_utils.h" +#include "node_errors.h" #include "node_snapshotable.h" #include "util.h" #include "v8.h" diff --git a/test/parallel/test-v8-cpu-profile.js b/test/parallel/test-v8-cpu-profile.js new file mode 100644 index 0000000000..3925e2a44f --- /dev/null +++ b/test/parallel/test-v8-cpu-profile.js @@ -0,0 +1,12 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const v8 = require('v8'); + +const handle = v8.startCpuProfile(); +const profile = handle.stop(); +assert.ok(typeof profile === 'string'); +assert.ok(profile.length > 0); +// Call stop() again +assert.ok(handle.stop() === undefined); diff --git a/tools/doc/type-parser.mjs b/tools/doc/type-parser.mjs index 3c97ec0f56..599de750d6 100644 --- a/tools/doc/type-parser.mjs +++ b/tools/doc/type-parser.mjs @@ -352,6 +352,7 @@ const customTypesMap = { 'LockManagerSnapshot': 'https://developer.mozilla.org/en-US/docs/Web/API/LockManagerSnapshot', 'CPUProfileHandle': 'v8.html#class-cpuprofilehandle', 'HeapProfileHandle': 'v8.html#class-heapprofilehandle', + 'SyncCPUProfileHandle': 'v8.html#class-synccpuprofilehandle', }; const arrayPart = /(?:\[])+$/;