| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1021,6 +1021,112 @@ added: v18.6.0 | |||
| 1021 | 1021 | ||
| 1022 | 1022 | Returns true if the Node.js instance is run to build a snapshot. | |
| 1023 | 1023 | ||
| 1024 | + ## Class: `v8.GCProfiler` | ||
| 1025 | + | ||
| 1026 | + <!-- YAML | ||
| 1027 | + added: REPLACEME | ||
| 1028 | + --> | ||
| 1029 | + | ||
| 1030 | + This API collects GC data in current thread. | ||
| 1031 | + | ||
| 1032 | + ### `new v8.GCProfiler()` | ||
| 1033 | + | ||
| 1034 | + <!-- YAML | ||
| 1035 | + added: REPLACEME | ||
| 1036 | + --> | ||
| 1037 | + | ||
| 1038 | + Create a new instance of the `v8.GCProfiler` class. | ||
| 1039 | + | ||
| 1040 | + ### `profiler.start()` | ||
| 1041 | + | ||
| 1042 | + <!-- YAML | ||
| 1043 | + added: REPLACEME | ||
| 1044 | + --> | ||
| 1045 | + | ||
| 1046 | + Start collecting GC data. | ||
| 1047 | + | ||
| 1048 | + ### `profiler.stop()` | ||
| 1049 | + | ||
| 1050 | + <!-- YAML | ||
| 1051 | + added: REPLACEME | ||
| 1052 | + --> | ||
| 1053 | + | ||
| 1054 | + Stop collecting GC data and return a object.The content of object | ||
| 1055 | + is as follows. | ||
| 1056 | + | ||
| 1057 | + ```json | ||
| 1058 | + { | ||
| 1059 | + "version": 1, | ||
| 1060 | + "startTime": 1674059033862, | ||
| 1061 | + "statistics": [ | ||
| 1062 | + { | ||
| 1063 | + "gcType": "Scavenge", | ||
| 1064 | + "beforeGC": { | ||
| 1065 | + "heapStatistics": { | ||
| 1066 | + "totalHeapSize": 5005312, | ||
| 1067 | + "totalHeapSizeExecutable": 524288, | ||
| 1068 | + "totalPhysicalSize": 5226496, | ||
| 1069 | + "totalAvailableSize": 4341325216, | ||
| 1070 | + "totalGlobalHandlesSize": 8192, | ||
| 1071 | + "usedGlobalHandlesSize": 2112, | ||
| 1072 | + "usedHeapSize": 4883840, | ||
| 1073 | + "heapSizeLimit": 4345298944, | ||
| 1074 | + "mallocedMemory": 254128, | ||
| 1075 | + "externalMemory": 225138, | ||
| 1076 | + "peakMallocedMemory": 181760 | ||
| 1077 | + }, | ||
| 1078 | + "heapSpaceStatistics": [ | ||
| 1079 | + { | ||
| 1080 | + "spaceName": "read_only_space", | ||
| 1081 | + "spaceSize": 0, | ||
| 1082 | + "spaceUsedSize": 0, | ||
| 1083 | + "spaceAvailableSize": 0, | ||
| 1084 | + "physicalSpaceSize": 0 | ||
| 1085 | + } | ||
| 1086 | + ] | ||
| 1087 | + }, | ||
| 1088 | + "cost": 1574.14, | ||
| 1089 | + "afterGC": { | ||
| 1090 | + "heapStatistics": { | ||
| 1091 | + "totalHeapSize": 6053888, | ||
| 1092 | + "totalHeapSizeExecutable": 524288, | ||
| 1093 | + "totalPhysicalSize": 5500928, | ||
| 1094 | + "totalAvailableSize": 4341101384, | ||
| 1095 | + "totalGlobalHandlesSize": 8192, | ||
| 1096 | + "usedGlobalHandlesSize": 2112, | ||
| 1097 | + "usedHeapSize": 4059096, | ||
| 1098 | + "heapSizeLimit": 4345298944, | ||
| 1099 | + "mallocedMemory": 254128, | ||
| 1100 | + "externalMemory": 225138, | ||
| 1101 | + "peakMallocedMemory": 181760 | ||
| 1102 | + }, | ||
| 1103 | + "heapSpaceStatistics": [ | ||
| 1104 | + { | ||
| 1105 | + "spaceName": "read_only_space", | ||
| 1106 | + "spaceSize": 0, | ||
| 1107 | + "spaceUsedSize": 0, | ||
| 1108 | + "spaceAvailableSize": 0, | ||
| 1109 | + "physicalSpaceSize": 0 | ||
| 1110 | + } | ||
| 1111 | + ] | ||
| 1112 | + } | ||
| 1113 | + } | ||
| 1114 | + ], | ||
| 1115 | + "endTime": 1674059036865 | ||
| 1116 | + } | ||
| 1117 | + ``` | ||
| 1118 | + | ||
| 1119 | + Here's an example. | ||
| 1120 | + | ||
| 1121 | + ```js | ||
| 1122 | + const { GCProfiler } = require('v8'); | ||
| 1123 | + const profiler = new GCProfiler(); | ||
| 1124 | + profiler.start(); | ||
| 1125 | + setTimeout(() => { | ||
| 1126 | + console.log(profiler.stop()); | ||
| 1127 | + }, 1000); | ||
| 1128 | + ``` | ||
| 1129 | + | ||
| 1024 | 1130 | [HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm | |
| 1025 | 1131 | [Hook Callbacks]: #hook-callbacks | |
| 1026 | 1132 | [V8]: https://developers.google.com/v8/ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -60,7 +60,7 @@ const { | |||
| 60 | 60 | const { HeapSnapshotStream } = require('internal/heap_utils'); | |
| 61 | 61 | const promiseHooks = require('internal/promise_hooks'); | |
| 62 | 62 | const { getOptionValue } = require('internal/options'); | |
| 63 | - | ||
| 63 | + const { JSONParse } = primordials; | ||
| 64 | 64 | /** | |
| 65 | 65 | * Generates a snapshot of the current V8 heap | |
| 66 | 66 | * and writes it to a JSON file. | |
@@ -384,6 +384,25 @@ function deserialize(buffer) { | |||
| 384 | 384 | return der.readValue(); | |
| 385 | 385 | } | |
| 386 | 386 | ||
| 387 | + class GCProfiler { | ||
| 388 | + #profiler = null; | ||
| 389 | + | ||
| 390 | + start() { | ||
| 391 | + if (!this.#profiler) { | ||
| 392 | + this.#profiler = new binding.GCProfiler(); | ||
| 393 | + this.#profiler.start(); | ||
| 394 | + } | ||
| 395 | + } | ||
| 396 | + | ||
| 397 | + stop() { | ||
| 398 | + if (this.#profiler) { | ||
| 399 | + const data = this.#profiler.stop(); | ||
| 400 | + this.#profiler = null; | ||
| 401 | + return JSONParse(data); | ||
| 402 | + } | ||
| 403 | + } | ||
| 404 | + } | ||
| 405 | + | ||
| 387 | 406 | module.exports = { | |
| 388 | 407 | cachedDataVersionTag, | |
| 389 | 408 | getHeapSnapshot, | |
@@ -403,4 +422,5 @@ module.exports = { | |||
| 403 | 422 | promiseHooks, | |
| 404 | 423 | startupSnapshot, | |
| 405 | 424 | setHeapSnapshotNearHeapLimit, | |
| 425 | + GCProfiler, | ||
| 406 | 426 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -33,6 +33,7 @@ namespace v8_utils { | |||
| 33 | 33 | using v8::Array; | |
| 34 | 34 | using v8::Context; | |
| 35 | 35 | using v8::FunctionCallbackInfo; | |
| 36 | + using v8::FunctionTemplate; | ||
| 36 | 37 | using v8::HandleScope; | |
| 37 | 38 | using v8::HeapCodeStatistics; | |
| 38 | 39 | using v8::HeapSpaceStatistics; | |
@@ -210,6 +211,184 @@ void SetFlagsFromString(const FunctionCallbackInfo<Value>& args) { | |||
| 210 | 211 | V8::SetFlagsFromString(*flags, static_cast<size_t>(flags.length())); | |
| 211 | 212 | } | |
| 212 | 213 | ||
| 214 | + static const char* GetGCTypeName(v8::GCType gc_type) { | ||
| 215 | + switch (gc_type) { | ||
| 216 | + case v8::GCType::kGCTypeScavenge: | ||
| 217 | + return "Scavenge"; | ||
| 218 | + case v8::GCType::kGCTypeMarkSweepCompact: | ||
| 219 | + return "MarkSweepCompact"; | ||
| 220 | + case v8::GCType::kGCTypeIncrementalMarking: | ||
| 221 | + return "IncrementalMarking"; | ||
| 222 | + case v8::GCType::kGCTypeProcessWeakCallbacks: | ||
| 223 | + return "ProcessWeakCallbacks"; | ||
| 224 | + default: | ||
| 225 | + return "Unknown"; | ||
| 226 | + } | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + static void SetHeapStatistics(JSONWriter* writer, Isolate* isolate) { | ||
| 230 | + HeapStatistics heap_statistics; | ||
| 231 | + isolate->GetHeapStatistics(&heap_statistics); | ||
| 232 | + writer->json_objectstart("heapStatistics"); | ||
| 233 | + writer->json_keyvalue("totalHeapSize", heap_statistics.total_heap_size()); | ||
| 234 | + writer->json_keyvalue("totalHeapSizeExecutable", | ||
| 235 | + heap_statistics.total_heap_size_executable()); | ||
| 236 | + writer->json_keyvalue("totalPhysicalSize", | ||
| 237 | + heap_statistics.total_physical_size()); | ||
| 238 | + writer->json_keyvalue("totalAvailableSize", | ||
| 239 | + heap_statistics.total_available_size()); | ||
| 240 | + writer->json_keyvalue("totalGlobalHandlesSize", | ||
| 241 | + heap_statistics.total_global_handles_size()); | ||
| 242 | + writer->json_keyvalue("usedGlobalHandlesSize", | ||
| 243 | + heap_statistics.used_global_handles_size()); | ||
| 244 | + writer->json_keyvalue("usedHeapSize", heap_statistics.used_heap_size()); | ||
| 245 | + writer->json_keyvalue("heapSizeLimit", heap_statistics.heap_size_limit()); | ||
| 246 | + writer->json_keyvalue("mallocedMemory", heap_statistics.malloced_memory()); | ||
| 247 | + writer->json_keyvalue("externalMemory", heap_statistics.external_memory()); | ||
| 248 | + writer->json_keyvalue("peakMallocedMemory", | ||
| 249 | + heap_statistics.peak_malloced_memory()); | ||
| 250 | + writer->json_objectend(); | ||
| 251 | + | ||
| 252 | + int space_count = isolate->NumberOfHeapSpaces(); | ||
| 253 | + writer->json_arraystart("heapSpaceStatistics"); | ||
| 254 | + for (int i = 0; i < space_count; i++) { | ||
| 255 | + HeapSpaceStatistics heap_space_statistics; | ||
| 256 | + isolate->GetHeapSpaceStatistics(&heap_space_statistics, i); | ||
| 257 | + writer->json_start(); | ||
| 258 | + writer->json_keyvalue("spaceName", heap_space_statistics.space_name()); | ||
| 259 | + writer->json_keyvalue("spaceSize", heap_space_statistics.space_size()); | ||
| 260 | + writer->json_keyvalue("spaceUsedSize", | ||
| 261 | + heap_space_statistics.space_used_size()); | ||
| 262 | + writer->json_keyvalue("spaceAvailableSize", | ||
| 263 | + heap_space_statistics.space_available_size()); | ||
| 264 | + writer->json_keyvalue("physicalSpaceSize", | ||
| 265 | + heap_space_statistics.physical_space_size()); | ||
| 266 | + writer->json_end(); | ||
| 267 | + } | ||
| 268 | + writer->json_arrayend(); | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + static void BeforeGCCallback(Isolate* isolate, | ||
| 272 | + v8::GCType gc_type, | ||
| 273 | + v8::GCCallbackFlags flags, | ||
| 274 | + void* data) { | ||
| 275 | + GCProfiler* profiler = static_cast<GCProfiler*>(data); | ||
| 276 | + if (profiler->current_gc_type != 0) { | ||
| 277 | + return; | ||
| 278 | + } | ||
| 279 | + JSONWriter* writer = profiler->writer(); | ||
| 280 | + writer->json_start(); | ||
| 281 | + writer->json_keyvalue("gcType", GetGCTypeName(gc_type)); | ||
| 282 | + writer->json_objectstart("beforeGC"); | ||
| 283 | + SetHeapStatistics(writer, isolate); | ||
| 284 | + writer->json_objectend(); | ||
| 285 | + profiler->current_gc_type = gc_type; | ||
| 286 | + profiler->start_time = uv_hrtime(); | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + static void AfterGCCallback(Isolate* isolate, | ||
| 290 | + v8::GCType gc_type, | ||
| 291 | + v8::GCCallbackFlags flags, | ||
| 292 | + void* data) { | ||
| 293 | + GCProfiler* profiler = static_cast<GCProfiler*>(data); | ||
| 294 | + if (profiler->current_gc_type != gc_type) { | ||
| 295 | + return; | ||
| 296 | + } | ||
| 297 | + JSONWriter* writer = profiler->writer(); | ||
| 298 | + profiler->current_gc_type = 0; | ||
| 299 | + writer->json_keyvalue("cost", (uv_hrtime() - profiler->start_time) / 1e3); | ||
| 300 | + profiler->start_time = 0; | ||
| 301 | + writer->json_objectstart("afterGC"); | ||
| 302 | + SetHeapStatistics(writer, isolate); | ||
| 303 | + writer->json_objectend(); | ||
| 304 | + writer->json_end(); | ||
| 305 | + } | ||
| 306 | + | ||
| 307 | + GCProfiler::GCProfiler(Environment* env, Local<Object> object) | ||
| 308 | + : BaseObject(env, object), | ||
| 309 | + start_time(0), | ||
| 310 | + current_gc_type(0), | ||
| 311 | + state(GCProfilerState::kInitialized), | ||
| 312 | + writer_(out_stream_, false) { | ||
| 313 | + MakeWeak(); | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + // This function will be called when | ||
| 317 | + // 1. StartGCProfile and StopGCProfile are called and | ||
| 318 | + // JS land does not keep the object anymore. | ||
| 319 | + // 2. StartGCProfile is called then the env exits before | ||
| 320 | + // StopGCProfile is called. | ||
| 321 | + GCProfiler::~GCProfiler() { | ||
| 322 | + if (state != GCProfiler::GCProfilerState::kInitialized) { | ||
| 323 | + env()->isolate()->RemoveGCPrologueCallback(BeforeGCCallback, this); | ||
| 324 | + env()->isolate()->RemoveGCEpilogueCallback(AfterGCCallback, this); | ||
| 325 | + } | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + JSONWriter* GCProfiler::writer() { | ||
| 329 | + return &writer_; | ||
| 330 | + } | ||
| 331 | + | ||
| 332 | + std::ostringstream* GCProfiler::out_stream() { | ||
| 333 | + return &out_stream_; | ||
| 334 | + } | ||
| 335 | + | ||
| 336 | + void GCProfiler::New(const FunctionCallbackInfo<Value>& args) { | ||
| 337 | + CHECK(args.IsConstructCall()); | ||
| 338 | + Environment* env = Environment::GetCurrent(args); | ||
| 339 | + new GCProfiler(env, args.This()); | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | + void GCProfiler::Start(const FunctionCallbackInfo<Value>& args) { | ||
| 343 | + Environment* env = Environment::GetCurrent(args); | ||
| 344 | + GCProfiler* profiler; | ||
| 345 | + ASSIGN_OR_RETURN_UNWRAP(&profiler, args.Holder()); | ||
| 346 | + if (profiler->state != GCProfiler::GCProfilerState::kInitialized) { | ||
| 347 | + return; | ||
| 348 | + } | ||
| 349 | + profiler->writer()->json_start(); | ||
| 350 | + profiler->writer()->json_keyvalue("version", 1); | ||
| 351 | + | ||
| 352 | + uv_timeval64_t ts; | ||
| 353 | + if (uv_gettimeofday(&ts) == 0) { | ||
| 354 | + profiler->writer()->json_keyvalue("startTime", | ||
| 355 | + ts.tv_sec * 1000 + ts.tv_usec / 1000); | ||
| 356 | + } else { | ||
| 357 | + profiler->writer()->json_keyvalue("startTime", 0); | ||
| 358 | + } | ||
| 359 | + profiler->writer()->json_arraystart("statistics"); | ||
| 360 | + env->isolate()->AddGCPrologueCallback(BeforeGCCallback, | ||
| 361 | + static_cast<void*>(profiler)); | ||
| 362 | + env->isolate()->AddGCEpilogueCallback(AfterGCCallback, | ||
| 363 | + static_cast<void*>(profiler)); | ||
| 364 | + profiler->state = GCProfiler::GCProfilerState::kStarted; | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + void GCProfiler::Stop(const FunctionCallbackInfo<v8::Value>& args) { | ||
| 368 | + Environment* env = Environment::GetCurrent(args); | ||
| 369 | + GCProfiler* profiler; | ||
| 370 | + ASSIGN_OR_RETURN_UNWRAP(&profiler, args.Holder()); | ||
| 371 | + if (profiler->state != GCProfiler::GCProfilerState::kStarted) { | ||
| 372 | + return; | ||
| 373 | + } | ||
| 374 | + profiler->writer()->json_arrayend(); | ||
| 375 | + uv_timeval64_t ts; | ||
| 376 | + if (uv_gettimeofday(&ts) == 0) { | ||
| 377 | + profiler->writer()->json_keyvalue("endTime", | ||
| 378 | + ts.tv_sec * 1000 + ts.tv_usec / 1000); | ||
| 379 | + } else { | ||
| 380 | + profiler->writer()->json_keyvalue("endTime", 0); | ||
| 381 | + } | ||
| 382 | + profiler->writer()->json_end(); | ||
| 383 | + profiler->state = GCProfiler::GCProfilerState::kStopped; | ||
| 384 | + auto string = profiler->out_stream()->str(); | ||
| 385 | + args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(), | ||
| 386 | + string.data(), | ||
| 387 | + v8::NewStringType::kNormal, | ||
| 388 | + string.size()) | ||
| 389 | + .ToLocalChecked()); | ||
| 390 | + } | ||
| 391 | + | ||
| 213 | 392 | void Initialize(Local<Object> target, | |
| 214 | 393 | Local<Value> unused, | |
| 215 | 394 | Local<Context> context, | |
@@ -272,6 +451,14 @@ void Initialize(Local<Object> target, | |||
| 272 | 451 | ||
| 273 | 452 | // Export symbols used by v8.setFlagsFromString() | |
| 274 | 453 | SetMethod(context, target, "setFlagsFromString", SetFlagsFromString); | |
| 454 | + | ||
| 455 | + // GCProfiler | ||
| 456 | + Local<FunctionTemplate> t = | ||
| 457 | + NewFunctionTemplate(env->isolate(), GCProfiler::New); | ||
| 458 | + t->InstanceTemplate()->SetInternalFieldCount(BaseObject::kInternalFieldCount); | ||
| 459 | + SetProtoMethod(env->isolate(), t, "start", GCProfiler::Start); | ||
| 460 | + SetProtoMethod(env->isolate(), t, "stop", GCProfiler::Stop); | ||
| 461 | + SetConstructorFunction(context, target, "GCProfiler", t); | ||
| 275 | 462 | } | |
| 276 | 463 | ||
| 277 | 464 | void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | |
@@ -281,6 +468,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | |||
| 281 | 468 | registry->Register(UpdateHeapSpaceStatisticsBuffer); | |
| 282 | 469 | registry->Register(SetFlagsFromString); | |
| 283 | 470 | registry->Register(SetHeapSnapshotNearHeapLimit); | |
| 471 | + registry->Register(GCProfiler::New); | ||
| 472 | + registry->Register(GCProfiler::Start); | ||
| 473 | + registry->Register(GCProfiler::Stop); | ||
| 284 | 474 | } | |
| 285 | 475 | ||
| 286 | 476 | } // namespace v8_utils | |
| Back | FazBrowse Home | New Git URL |
0 commit comments