| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1421,6 +1421,33 @@ added: v24.8.0 | |||
| 1421 | 1421 | ||
| 1422 | 1422 | Stopping collecting the profile and the profile will be discarded. | |
| 1423 | 1423 | ||
| 1424 | + ## Class: `HeapProfileHandle` | ||
| 1425 | + | ||
| 1426 | + <!-- YAML | ||
| 1427 | + added: REPLACEME | ||
| 1428 | + --> | ||
| 1429 | + | ||
| 1430 | + ### `heapProfileHandle.stop()` | ||
| 1431 | + | ||
| 1432 | + <!-- YAML | ||
| 1433 | + added: REPLACEME | ||
| 1434 | + --> | ||
| 1435 | + | ||
| 1436 | + * Returns: {Promise} | ||
| 1437 | + | ||
| 1438 | + Stopping collecting the profile, then return a Promise that fulfills with an error or the | ||
| 1439 | + profile data. | ||
| 1440 | + | ||
| 1441 | + ### `heapProfileHandle[Symbol.asyncDispose]()` | ||
| 1442 | + | ||
| 1443 | + <!-- YAML | ||
| 1444 | + added: REPLACEME | ||
| 1445 | + --> | ||
| 1446 | + | ||
| 1447 | + * Returns: {Promise} | ||
| 1448 | + | ||
| 1449 | + Stopping collecting the profile and the profile will be discarded. | ||
| 1450 | + | ||
| 1424 | 1451 | ## `v8.isStringOneByteRepresentation(content)` | |
| 1425 | 1452 | ||
| 1426 | 1453 | <!-- YAML | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1996,6 +1996,49 @@ w.on('online', async () => { | |||
| 1996 | 1996 | }); | |
| 1997 | 1997 | ``` | |
| 1998 | 1998 | ||
| 1999 | + ### `worker.startHeapProfile()` | ||
| 2000 | + | ||
| 2001 | + <!-- YAML | ||
| 2002 | + added: REPLACEME | ||
| 2003 | + --> | ||
| 2004 | + | ||
| 2005 | + * Returns: {Promise} | ||
| 2006 | + | ||
| 2007 | + Starting a Heap profile then return a Promise that fulfills with an error | ||
| 2008 | + or an `HeapProfileHandle` object. This API supports `await using` syntax. | ||
| 2009 | + | ||
| 2010 | + ```cjs | ||
| 2011 | + const { Worker } = require('node:worker_threads'); | ||
| 2012 | + | ||
| 2013 | + const worker = new Worker(` | ||
| 2014 | + const { parentPort } = require('worker_threads'); | ||
| 2015 | + parentPort.on('message', () => {}); | ||
| 2016 | + `, { eval: true }); | ||
| 2017 | + | ||
| 2018 | + worker.on('online', async () => { | ||
| 2019 | + const handle = await worker.startHeapProfile(); | ||
| 2020 | + const profile = await handle.stop(); | ||
| 2021 | + console.log(profile); | ||
| 2022 | + worker.terminate(); | ||
| 2023 | + }); | ||
| 2024 | + ``` | ||
| 2025 | + | ||
| 2026 | + `await using` example. | ||
| 2027 | + | ||
| 2028 | + ```cjs | ||
| 2029 | + const { Worker } = require('node::worker_threads'); | ||
| 2030 | + | ||
| 2031 | + const w = new Worker(` | ||
| 2032 | + const { parentPort } = require('worker_threads'); | ||
| 2033 | + parentPort.on('message', () => {}); | ||
| 2034 | + `, { eval: true }); | ||
| 2035 | + | ||
| 2036 | + w.on('online', async () => { | ||
| 2037 | + // Stop profile automatically when return and profile will be discarded | ||
| 2038 | + await using handle = await w.startHeapProfile(); | ||
| 2039 | + }); | ||
| 2040 | + ``` | ||
| 2041 | + | ||
| 1999 | 2042 | ### `worker.stderr` | |
| 2000 | 2043 | ||
| 2001 | 2044 | <!-- YAML | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -164,6 +164,35 @@ class CPUProfileHandle { | |||
| 164 | 164 | } | |
| 165 | 165 | } | |
| 166 | 166 | ||
| 167 | + class HeapProfileHandle { | ||
| 168 | + #worker = null; | ||
| 169 | + #promise = null; | ||
| 170 | + | ||
| 171 | + constructor(worker) { | ||
| 172 | + this.#worker = worker; | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + stop() { | ||
| 176 | + if (this.#promise) { | ||
| 177 | + return this.#promise; | ||
| 178 | + } | ||
| 179 | + const stopTaker = this.#worker[kHandle]?.stopHeapProfile(); | ||
| 180 | + return this.#promise = new Promise((resolve, reject) => { | ||
| 181 | + if (!stopTaker) return reject(new ERR_WORKER_NOT_RUNNING()); | ||
| 182 | + stopTaker.ondone = (err, profile) => { | ||
| 183 | + if (err) { | ||
| 184 | + return reject(err); | ||
| 185 | + } | ||
| 186 | + resolve(profile); | ||
| 187 | + }; | ||
| 188 | + }); | ||
| 189 | + }; | ||
| 190 | + | ||
| 191 | + async [SymbolAsyncDispose]() { | ||
| 192 | + await this.stop(); | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | + | ||
| 167 | 196 | class Worker extends EventEmitter { | |
| 168 | 197 | constructor(filename, options = kEmptyObject) { | |
| 169 | 198 | throwIfBuildingSnapshot('Creating workers'); | |
@@ -559,6 +588,19 @@ class Worker extends EventEmitter { | |||
| 559 | 588 | }; | |
| 560 | 589 | }); | |
| 561 | 590 | } | |
| 591 | + | ||
| 592 | + startHeapProfile() { | ||
| 593 | + const startTaker = this[kHandle]?.startHeapProfile(); | ||
| 594 | + return new Promise((resolve, reject) => { | ||
| 595 | + if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING()); | ||
| 596 | + startTaker.ondone = (err) => { | ||
| 597 | + if (err) { | ||
| 598 | + return reject(err); | ||
| 599 | + } | ||
| 600 | + resolve(new HeapProfileHandle(this)); | ||
| 601 | + }; | ||
| 602 | + }); | ||
| 603 | + } | ||
| 562 | 604 | } | |
| 563 | 605 | ||
| 564 | 606 | /** | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -81,6 +81,7 @@ namespace node { | |||
| 81 | 81 | V(WORKER) \ | |
| 82 | 82 | V(WORKERCPUPROFILE) \ | |
| 83 | 83 | V(WORKERCPUUSAGE) \ | |
| 84 | + V(WORKERHEAPPROFILE) \ | ||
| 84 | 85 | V(WORKERHEAPSNAPSHOT) \ | |
| 85 | 86 | V(WORKERHEAPSTATISTICS) \ | |
| 86 | 87 | V(WRITEWRAP) \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -458,6 +458,7 @@ | |||
| 458 | 458 | V(write_wrap_template, v8::ObjectTemplate) \ | |
| 459 | 459 | V(worker_cpu_profile_taker_template, v8::ObjectTemplate) \ | |
| 460 | 460 | V(worker_cpu_usage_taker_template, v8::ObjectTemplate) \ | |
| 461 | + V(worker_heap_profile_taker_template, v8::ObjectTemplate) \ | ||
| 461 | 462 | V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \ | |
| 462 | 463 | V(worker_heap_statistics_taker_template, v8::ObjectTemplate) \ | |
| 463 | 464 | V(x509_constructor_template, v8::FunctionTemplate) \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -87,6 +87,8 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); | |||
| 87 | 87 | V(ERR_FS_CP_SOCKET, Error) \ | |
| 88 | 88 | V(ERR_FS_CP_FIFO_PIPE, Error) \ | |
| 89 | 89 | V(ERR_FS_CP_UNKNOWN, Error) \ | |
| 90 | + V(ERR_HEAP_PROFILE_HAVE_BEEN_STARTED, Error) \ | ||
| 91 | + V(ERR_HEAP_PROFILE_NOT_STARTED, Error) \ | ||
| 90 | 92 | V(ERR_ILLEGAL_CONSTRUCTOR, Error) \ | |
| 91 | 93 | V(ERR_INVALID_ADDRESS, Error) \ | |
| 92 | 94 | V(ERR_INVALID_ARG_VALUE, TypeError) \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,6 +20,7 @@ | |||
| 20 | 20 | ||
| 21 | 21 | using node::kAllowedInEnvvar; | |
| 22 | 22 | using node::kDisallowedInEnvvar; | |
| 23 | + using v8::AllocationProfile; | ||
| 23 | 24 | using v8::Array; | |
| 24 | 25 | using v8::ArrayBuffer; | |
| 25 | 26 | using v8::Boolean; | |
@@ -32,6 +33,7 @@ using v8::Float64Array; | |||
| 32 | 33 | using v8::FunctionCallbackInfo; | |
| 33 | 34 | using v8::FunctionTemplate; | |
| 34 | 35 | using v8::HandleScope; | |
| 36 | + using v8::HeapProfiler; | ||
| 35 | 37 | using v8::HeapStatistics; | |
| 36 | 38 | using v8::Integer; | |
| 37 | 39 | using v8::Isolate; | |
@@ -1031,6 +1033,169 @@ void Worker::StopCpuProfile(const FunctionCallbackInfo<Value>& args) { | |||
| 1031 | 1033 | } | |
| 1032 | 1034 | } | |
| 1033 | 1035 | ||
| 1036 | + class WorkerHeapProfileTaker final : public AsyncWrap { | ||
| 1037 | + public: | ||
| 1038 | + WorkerHeapProfileTaker(Environment* env, Local<Object> obj) | ||
| 1039 | + : AsyncWrap(env, obj, AsyncWrap::PROVIDER_WORKERHEAPPROFILE) {} | ||
| 1040 | + | ||
| 1041 | + SET_NO_MEMORY_INFO() | ||
| 1042 | + SET_MEMORY_INFO_NAME(WorkerHeapProfileTaker) | ||
| 1043 | + SET_SELF_SIZE(WorkerHeapProfileTaker) | ||
| 1044 | + }; | ||
| 1045 | + | ||
| 1046 | + void Worker::StartHeapProfile(const FunctionCallbackInfo<Value>& args) { | ||
| 1047 | + Worker* w; | ||
| 1048 | + ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); | ||
| 1049 | + Environment* env = w->env(); | ||
| 1050 | + | ||
| 1051 | + AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); | ||
| 1052 | + Local<Object> wrap; | ||
| 1053 | + if (!env->worker_heap_profile_taker_template() | ||
| 1054 | + ->NewInstance(env->context()) | ||
| 1055 | + .ToLocal(&wrap)) { | ||
| 1056 | + return; | ||
| 1057 | + } | ||
| 1058 | + | ||
| 1059 | + BaseObjectPtr<WorkerHeapProfileTaker> taker = | ||
| 1060 | + MakeDetachedBaseObject<WorkerHeapProfileTaker>(env, wrap); | ||
| 1061 | + | ||
| 1062 | + bool scheduled = w->RequestInterrupt([taker = std::move(taker), | ||
| 1063 | + env](Environment* worker_env) mutable { | ||
| 1064 | + v8::HeapProfiler* profiler = worker_env->isolate()->GetHeapProfiler(); | ||
| 1065 | + bool success = profiler->StartSamplingHeapProfiler(); | ||
| 1066 | + env->SetImmediateThreadsafe( | ||
| 1067 | + [taker = std::move(taker), | ||
| 1068 | + success = success](Environment* env) mutable { | ||
| 1069 | + Isolate* isolate = env->isolate(); | ||
| 1070 | + HandleScope handle_scope(isolate); | ||
| 1071 | + Context::Scope context_scope(env->context()); | ||
| 1072 | + AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get()); | ||
| 1073 | + Local<Value> argv[] = { | ||
| 1074 | + Null(isolate), // error | ||
| 1075 | + }; | ||
| 1076 | + if (!success) { | ||
| 1077 | + argv[0] = ERR_HEAP_PROFILE_HAVE_BEEN_STARTED( | ||
| 1078 | + isolate, "heap profiler have been started"); | ||
| 1079 | + } | ||
| 1080 | + taker->MakeCallback(env->ondone_string(), arraysize(argv), argv); | ||
| 1081 | + }, | ||
| 1082 | + CallbackFlags::kUnrefed); | ||
| 1083 | + }); | ||
| 1084 | + | ||
| 1085 | + if (scheduled) { | ||
| 1086 | + args.GetReturnValue().Set(wrap); | ||
| 1087 | + } | ||
| 1088 | + } | ||
| 1089 | + | ||
| 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 | + | ||
| 1147 | + void Worker::StopHeapProfile(const FunctionCallbackInfo<Value>& args) { | ||
| 1148 | + Worker* w; | ||
| 1149 | + ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); | ||
| 1150 | + | ||
| 1151 | + Environment* env = w->env(); | ||
| 1152 | + AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(w); | ||
| 1153 | + Local<Object> wrap; | ||
| 1154 | + if (!env->worker_heap_profile_taker_template() | ||
| 1155 | + ->NewInstance(env->context()) | ||
| 1156 | + .ToLocal(&wrap)) { | ||
| 1157 | + return; | ||
| 1158 | + } | ||
| 1159 | + | ||
| 1160 | + BaseObjectPtr<WorkerHeapProfileTaker> taker = | ||
| 1161 | + MakeDetachedBaseObject<WorkerHeapProfileTaker>(env, wrap); | ||
| 1162 | + | ||
| 1163 | + bool scheduled = w->RequestInterrupt([taker = std::move(taker), | ||
| 1164 | + env](Environment* worker_env) mutable { | ||
| 1165 | + std::ostringstream out_stream; | ||
| 1166 | + bool success = serializeProfile(worker_env->isolate(), out_stream); | ||
| 1167 | + env->SetImmediateThreadsafe( | ||
| 1168 | + [taker = std::move(taker), | ||
| 1169 | + out_stream = std::move(out_stream), | ||
| 1170 | + success = success](Environment* env) mutable { | ||
| 1171 | + Isolate* isolate = env->isolate(); | ||
| 1172 | + HandleScope handle_scope(isolate); | ||
| 1173 | + Context::Scope context_scope(env->context()); | ||
| 1174 | + AsyncHooks::DefaultTriggerAsyncIdScope trigger_id_scope(taker.get()); | ||
| 1175 | + Local<Value> argv[] = { | ||
| 1176 | + Null(isolate), // error | ||
| 1177 | + Undefined(isolate), // profile | ||
| 1178 | + }; | ||
| 1179 | + if (success) { | ||
| 1180 | + Local<Value> result; | ||
| 1181 | + if (!ToV8Value(env->context(), out_stream.str(), isolate) | ||
| 1182 | + .ToLocal(&result)) { | ||
| 1183 | + return; | ||
| 1184 | + } | ||
| 1185 | + argv[1] = result; | ||
| 1186 | + } else { | ||
| 1187 | + argv[0] = ERR_HEAP_PROFILE_NOT_STARTED(isolate, | ||
| 1188 | + "heap profile not started"); | ||
| 1189 | + } | ||
| 1190 | + taker->MakeCallback(env->ondone_string(), arraysize(argv), argv); | ||
| 1191 | + }, | ||
| 1192 | + CallbackFlags::kUnrefed); | ||
| 1193 | + }); | ||
| 1194 | + | ||
| 1195 | + if (scheduled) { | ||
| 1196 | + args.GetReturnValue().Set(wrap); | ||
| 1197 | + } | ||
| 1198 | + } | ||
| 1034 | 1199 | class WorkerHeapStatisticsTaker : public AsyncWrap { | |
| 1035 | 1200 | public: | |
| 1036 | 1201 | WorkerHeapStatisticsTaker(Environment* env, Local<Object> obj) | |
@@ -1328,6 +1493,8 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, | |||
| 1328 | 1493 | SetProtoMethod(isolate, w, "cpuUsage", Worker::CpuUsage); | |
| 1329 | 1494 | SetProtoMethod(isolate, w, "startCpuProfile", Worker::StartCpuProfile); | |
| 1330 | 1495 | SetProtoMethod(isolate, w, "stopCpuProfile", Worker::StopCpuProfile); | |
| 1496 | + SetProtoMethod(isolate, w, "startHeapProfile", Worker::StartHeapProfile); | ||
| 1497 | + SetProtoMethod(isolate, w, "stopHeapProfile", Worker::StopHeapProfile); | ||
| 1331 | 1498 | ||
| 1332 | 1499 | SetConstructorFunction(isolate, target, "Worker", w); | |
| 1333 | 1500 | } | |
@@ -1387,6 +1554,20 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, | |||
| 1387 | 1554 | wst->InstanceTemplate()); | |
| 1388 | 1555 | } | |
| 1389 | 1556 | ||
| 1557 | + { | ||
| 1558 | + Local<FunctionTemplate> wst = NewFunctionTemplate(isolate, nullptr); | ||
| 1559 | + | ||
| 1560 | + wst->InstanceTemplate()->SetInternalFieldCount( | ||
| 1561 | + WorkerHeapProfileTaker::kInternalFieldCount); | ||
| 1562 | + wst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); | ||
| 1563 | + | ||
| 1564 | + Local<String> wst_string = | ||
| 1565 | + FIXED_ONE_BYTE_STRING(isolate, "WorkerHeapProfileTaker"); | ||
| 1566 | + wst->SetClassName(wst_string); | ||
| 1567 | + isolate_data->set_worker_heap_profile_taker_template( | ||
| 1568 | + wst->InstanceTemplate()); | ||
| 1569 | + } | ||
| 1570 | + | ||
| 1390 | 1571 | SetMethod(isolate, target, "getEnvMessagePort", GetEnvMessagePort); | |
| 1391 | 1572 | } | |
| 1392 | 1573 | ||
@@ -1466,6 +1647,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | |||
| 1466 | 1647 | registry->Register(Worker::CpuUsage); | |
| 1467 | 1648 | registry->Register(Worker::StartCpuProfile); | |
| 1468 | 1649 | registry->Register(Worker::StopCpuProfile); | |
| 1650 | + registry->Register(Worker::StartHeapProfile); | ||
| 1651 | + registry->Register(Worker::StopHeapProfile); | ||
| 1469 | 1652 | } | |
| 1470 | 1653 | ||
| 1471 | 1654 | } // anonymous namespace | |
| Back | FazBrowse Home | New Git URL |
0 commit comments