| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a2f39e9 commit 90b5ed1
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -474,6 +474,39 @@ void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) { | |||
| 474 | 474 | return args.GetReturnValue().Set(filename_v); | |
| 475 | 475 | } | |
| 476 | 476 | ||
| 477 | + class PrototypeChainHas : public v8::QueryObjectPredicate { | ||
| 478 | + public: | ||
| 479 | + PrototypeChainHas(Local<Context> context, Local<Object> search) | ||
| 480 | + : context_(context), search_(search) {} | ||
| 481 | + | ||
| 482 | + // What we can do in the filter can be quite limited, but looking up | ||
| 483 | + // the prototype chain is something that the inspector console API | ||
| 484 | + // queryObject() does so it is supported. | ||
| 485 | + bool Filter(Local<Object> object) override { | ||
| 486 | + for (Local<Value> proto = object->GetPrototype(); proto->IsObject(); | ||
| 487 | + proto = proto.As<Object>()->GetPrototype()) { | ||
| 488 | + if (search_ == proto) return true; | ||
| 489 | + } | ||
| 490 | + return false; | ||
| 491 | + } | ||
| 492 | + | ||
| 493 | + private: | ||
| 494 | + Local<Context> context_; | ||
| 495 | + Local<Object> search_; | ||
| 496 | + }; | ||
| 497 | + | ||
| 498 | + void CountObjectsWithPrototype(const FunctionCallbackInfo<Value>& args) { | ||
| 499 | + CHECK_EQ(args.Length(), 1); | ||
| 500 | + CHECK(args[0]->IsObject()); | ||
| 501 | + Local<Object> proto = args[0].As<Object>(); | ||
| 502 | + Isolate* isolate = args.GetIsolate(); | ||
| 503 | + Local<Context> context = isolate->GetCurrentContext(); | ||
| 504 | + PrototypeChainHas prototype_chain_has(context, proto); | ||
| 505 | + std::vector<Global<Object>> out; | ||
| 506 | + isolate->GetHeapProfiler()->QueryObjects(context, &prototype_chain_has, &out); | ||
| 507 | + args.GetReturnValue().Set(static_cast<uint32_t>(out.size())); | ||
| 508 | + } | ||
| 509 | + | ||
| 477 | 510 | void Initialize(Local<Object> target, | |
| 478 | 511 | Local<Value> unused, | |
| 479 | 512 | Local<Context> context, | |
@@ -482,12 +515,15 @@ void Initialize(Local<Object> target, | |||
| 482 | 515 | SetMethod(context, target, "triggerHeapSnapshot", TriggerHeapSnapshot); | |
| 483 | 516 | SetMethod( | |
| 484 | 517 | context, target, "createHeapSnapshotStream", CreateHeapSnapshotStream); | |
| 518 | + SetMethod( | ||
| 519 | + context, target, "countObjectsWithPrototype", CountObjectsWithPrototype); | ||
| 485 | 520 | } | |
| 486 | 521 | ||
| 487 | 522 | void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | |
| 488 | 523 | registry->Register(BuildEmbedderGraph); | |
| 489 | 524 | registry->Register(TriggerHeapSnapshot); | |
| 490 | 525 | registry->Register(CreateHeapSnapshotStream); | |
| 526 | + registry->Register(CountObjectsWithPrototype); | ||
| 491 | 527 | } | |
| 492 | 528 | ||
| 493 | 529 | } // namespace heap | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -75,7 +75,54 @@ async function runAndBreathe(fn, repeat, waitTime = 20) { | |||
| 75 | 75 | } | |
| 76 | 76 | } | |
| 77 | 77 | ||
| 78 | + /** | ||
| 79 | + * This requires --expose-internals. | ||
| 80 | + * This function can be used to check if an object factory leaks or not by | ||
| 81 | + * iterating over the heap and count objects with the specified class | ||
| 82 | + * (which is checked by looking up the prototype chain). | ||
| 83 | + * @param {(i: number) => number} fn The factory receiving iteration count | ||
| 84 | + * and returning number of objects created. The return value should be | ||
| 85 | + * precise otherwise false negatives can be produced. | ||
| 86 | + * @param {Function} klass The class whose object is used to count the objects | ||
| 87 | + * @param {number} count Number of iterations that this check should be done | ||
| 88 | + * @param {number} waitTime Optional breathing time for GC. | ||
| 89 | + */ | ||
| 90 | + async function checkIfCollectableByCounting(fn, klass, count, waitTime = 20) { | ||
| 91 | + const { internalBinding } = require('internal/test/binding'); | ||
| 92 | + const { countObjectsWithPrototype } = internalBinding('heap_utils'); | ||
| 93 | + const { prototype, name } = klass; | ||
| 94 | + const initialCount = countObjectsWithPrototype(prototype); | ||
| 95 | + console.log(`Initial count of ${name}: ${initialCount}`); | ||
| 96 | + let totalCreated = 0; | ||
| 97 | + for (let i = 0; i < count; ++i) { | ||
| 98 | + const created = await fn(i); | ||
| 99 | + totalCreated += created; | ||
| 100 | + console.log(`#${i}: created ${created} ${name}, total ${totalCreated}`); | ||
| 101 | + await wait(waitTime); // give GC some breathing room. | ||
| 102 | + const currentCount = countObjectsWithPrototype(prototype); | ||
| 103 | + const collected = totalCreated - (currentCount - initialCount); | ||
| 104 | + console.log(`#${i}: counted ${currentCount} ${name}, collected ${collected}`); | ||
| 105 | + if (collected > 0) { | ||
| 106 | + console.log(`Detected ${collected} collected ${name}, finish early`); | ||
| 107 | + return; | ||
| 108 | + } | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + await wait(waitTime); // give GC some breathing room. | ||
| 112 | + const currentCount = countObjectsWithPrototype(prototype); | ||
| 113 | + const collected = totalCreated - (currentCount - initialCount); | ||
| 114 | + console.log(`Last count: counted ${currentCount} ${name}, collected ${collected}`); | ||
| 115 | + // Some objects with the prototype can be collected. | ||
| 116 | + if (collected > 0) { | ||
| 117 | + console.log(`Detected ${collected} collected ${name}`); | ||
| 118 | + return; | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + throw new Error(`${name} cannot be collected`); | ||
| 122 | + } | ||
| 123 | + | ||
| 78 | 124 | module.exports = { | |
| 79 | 125 | checkIfCollectable, | |
| 80 | 126 | runAndBreathe, | |
| 127 | + checkIfCollectableByCounting, | ||
| 81 | 128 | }; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments