mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
Bug 1067491 - Add allocations recording to the memory actor. r=jryans
This commit is contained in:
parent
aa874f56a5
commit
9d28679208
@ -6,8 +6,9 @@
|
||||
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
let protocol = require("devtools/server/protocol");
|
||||
let { method, RetVal } = protocol;
|
||||
let { method, RetVal, Arg } = protocol;
|
||||
const { reportException } = require("devtools/toolkit/DevToolsUtils");
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
|
||||
/**
|
||||
* A method decorator that ensures the actor is in the expected state before
|
||||
@ -59,9 +60,18 @@ let MemoryActor = protocol.ActorClass({
|
||||
.getService(Ci.nsIMemoryReporterManager);
|
||||
this.state = "detached";
|
||||
this._dbg = null;
|
||||
this._framesToCounts = null;
|
||||
this._framesToIndices = null;
|
||||
this._framesToForms = null;
|
||||
|
||||
this._onWindowReady = this._onWindowReady.bind(this);
|
||||
|
||||
events.on(this.parent, "window-ready", this._onWindowReady);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
events.off(this.parent, "window-ready", this._onWindowReady);
|
||||
|
||||
this._mgr = null;
|
||||
if (this.state === "attached") {
|
||||
this.detach();
|
||||
@ -74,7 +84,6 @@ let MemoryActor = protocol.ActorClass({
|
||||
*/
|
||||
attach: method(expectState("detached", function() {
|
||||
this.dbg.addDebuggees();
|
||||
this.dbg.enabled = true;
|
||||
this.state = "attached";
|
||||
}), {
|
||||
request: {},
|
||||
@ -87,7 +96,7 @@ let MemoryActor = protocol.ActorClass({
|
||||
* Detach from this MemoryActor.
|
||||
*/
|
||||
detach: method(expectState("attached", function() {
|
||||
this.dbg.removeAllDebuggees();
|
||||
this._clearDebuggees();
|
||||
this.dbg.enabled = false;
|
||||
this._dbg = null;
|
||||
this.state = "detached";
|
||||
@ -98,6 +107,260 @@ let MemoryActor = protocol.ActorClass({
|
||||
}
|
||||
}),
|
||||
|
||||
_clearDebuggees: function() {
|
||||
if (this._dbg) {
|
||||
if (this.dbg.memory.trackingAllocationSites) {
|
||||
this.dbg.memory.drainAllocationsLog();
|
||||
}
|
||||
this._clearFrames();
|
||||
this.dbg.removeAllDebuggees();
|
||||
}
|
||||
},
|
||||
|
||||
_initFrames: function() {
|
||||
this._framesToCounts = new Map();
|
||||
this._framesToIndices = new Map();
|
||||
this._framesToForms = new Map();
|
||||
},
|
||||
|
||||
_clearFrames: function() {
|
||||
if (this.dbg.memory.trackingAllocationSites) {
|
||||
this._framesToCounts.clear();
|
||||
this._framesToCounts = null;
|
||||
this._framesToIndices.clear();
|
||||
this._framesToIndices = null;
|
||||
this._framesToForms.clear();
|
||||
this._framesToForms = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the parent actor's "window-ready" event.
|
||||
*/
|
||||
_onWindowReady: function({ isTopLevel }) {
|
||||
if (this.state == "attached") {
|
||||
if (isTopLevel && this.dbg.memory.trackingAllocationSites) {
|
||||
this._clearDebuggees();
|
||||
this._initFrames();
|
||||
}
|
||||
this.dbg.addDebuggees();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
|
||||
* more information.
|
||||
*/
|
||||
takeCensus: method(expectState("attached", function() {
|
||||
return this.dbg.memory.takeCensus();
|
||||
}), {
|
||||
request: {},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
/**
|
||||
* Start recording allocation sites.
|
||||
*/
|
||||
startRecordingAllocations: method(expectState("attached", function() {
|
||||
this._initFrames();
|
||||
this.dbg.memory.trackingAllocationSites = true;
|
||||
}), {
|
||||
request: {},
|
||||
response: {}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stop recording allocation sites.
|
||||
*/
|
||||
stopRecordingAllocations: method(expectState("attached", function(shouldRecord) {
|
||||
this.dbg.memory.trackingAllocationSites = false;
|
||||
this._clearFrames();
|
||||
}), {
|
||||
request: {},
|
||||
response: {}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get a list of the most recent allocations since the last time we got
|
||||
* allocations, as well as a summary of all allocations since we've been
|
||||
* recording.
|
||||
*
|
||||
* @returns Object
|
||||
* An object of the form:
|
||||
*
|
||||
* {
|
||||
* allocations: [<index into "frames" below> ...],
|
||||
* frames: [
|
||||
* {
|
||||
* line: <line number for this frame>,
|
||||
* column: <column number for this frame>,
|
||||
* source: <filename string for this frame>,
|
||||
* functionDisplayName: <this frame's inferred function name function or null>,
|
||||
* parent: <index into "frames">
|
||||
* }
|
||||
* ...
|
||||
* ],
|
||||
* counts: [
|
||||
* <number of allocations in frames[0]>,
|
||||
* <number of allocations in frames[1]>,
|
||||
* <number of allocations in frames[2]>,
|
||||
* ...
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* Subsequent `getAllocations` request within the same recording and
|
||||
* tab navigation will always place the same stack frames at the same
|
||||
* indices as previous `getAllocations` requests in the same
|
||||
* recording. In other words, it is safe to use the index as a
|
||||
* unique, persistent id for its frame.
|
||||
*
|
||||
* Additionally, the root node (null) is always at index 0.
|
||||
*
|
||||
* Note that the allocation counts include "self" allocations only,
|
||||
* and don't account for allocations in child frames.
|
||||
*
|
||||
* We use the indices into the "frames" array to avoid repeating the
|
||||
* description of duplicate stack frames both when listing
|
||||
* allocations, and when many stacks share the same tail of older
|
||||
* frames. There shouldn't be any duplicates in the "frames" array,
|
||||
* as that would defeat the purpose of this compression trick.
|
||||
*
|
||||
* In the future, we might want to split out a frame's "source" and
|
||||
* "functionDisplayName" properties out the same way we have split
|
||||
* frames out with the "frames" array. While this would further
|
||||
* compress the size of the response packet, it would increase CPU
|
||||
* usage to build the packet, and it should, of course, be guided by
|
||||
* profiling and done only when necessary.
|
||||
*/
|
||||
getAllocations: method(expectState("attached", function() {
|
||||
const allocations = this.dbg.memory.drainAllocationsLog()
|
||||
const packet = {
|
||||
allocations: []
|
||||
};
|
||||
|
||||
for (let stack of allocations) {
|
||||
if (stack && Cu.isDeadWrapper(stack)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Safe because SavedFrames are frozen/immutable.
|
||||
let waived = Cu.waiveXrays(stack);
|
||||
|
||||
// Ensure that we have a form, count, and index for new allocations
|
||||
// because we potentially haven't seen some or all of them yet. After this
|
||||
// loop, we can rely on the fact that every frame we deal with already has
|
||||
// its metadata stored.
|
||||
this._assignFrameIndices(waived);
|
||||
this._createFrameForms(waived);
|
||||
this._countFrame(waived);
|
||||
|
||||
packet.allocations.push(this._framesToIndices.get(waived));
|
||||
}
|
||||
|
||||
// Now that we are guaranteed to have a form for every frame, we know the
|
||||
// size the "frames" property's array must be. We use that information to
|
||||
// create dense arrays even though we populate them out of order.
|
||||
const size = this._framesToForms.size;
|
||||
packet.frames = Array(size).fill(null);
|
||||
packet.counts = Array(size).fill(0);
|
||||
|
||||
// Populate the "frames" and "counts" properties.
|
||||
for (let [stack, index] of this._framesToIndices) {
|
||||
packet.frames[index] = this._framesToForms.get(stack);
|
||||
packet.counts[index] = this._framesToCounts.get(stack) || 0;
|
||||
}
|
||||
|
||||
return packet;
|
||||
}), {
|
||||
request: {},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
/**
|
||||
* Assigns an index to the given frame and its parents, if an index is not
|
||||
* already assigned.
|
||||
*
|
||||
* @param SavedFrame frame
|
||||
* A frame to assign an index to.
|
||||
*/
|
||||
_assignFrameIndices: function(frame) {
|
||||
if (this._framesToIndices.has(frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame) {
|
||||
this._assignFrameIndices(frame.parent);
|
||||
}
|
||||
|
||||
const index = this._framesToIndices.size;
|
||||
this._framesToIndices.set(frame, index);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create the form for the given frame, if one doesn't already exist.
|
||||
*
|
||||
* @param SavedFrame frame
|
||||
* A frame to create a form for.
|
||||
*/
|
||||
_createFrameForms: function(frame) {
|
||||
if (this._framesToForms.has(frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let form = null;
|
||||
if (frame) {
|
||||
form = {
|
||||
line: frame.line,
|
||||
column: frame.column,
|
||||
source: frame.source,
|
||||
functionDisplayName: frame.functionDisplayName,
|
||||
parent: this._framesToIndices.get(frame.parent)
|
||||
};
|
||||
this._createFrameForms(frame.parent);
|
||||
}
|
||||
|
||||
this._framesToForms.set(frame, form);
|
||||
},
|
||||
|
||||
/**
|
||||
* Increment the allocation count for the provided frame.
|
||||
*
|
||||
* @param SavedFrame frame
|
||||
* The frame whose allocation count should be incremented.
|
||||
*/
|
||||
_countFrame: function(frame) {
|
||||
if (!this._framesToCounts.has(frame)) {
|
||||
this._framesToCounts.set(frame, 1);
|
||||
} else {
|
||||
let count = this._framesToCounts.get(frame);
|
||||
this._framesToCounts.set(frame, count + 1);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Force a browser-wide GC.
|
||||
*/
|
||||
forceGarbageCollection: method(function() {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
Cu.forceGC();
|
||||
}
|
||||
}, {
|
||||
request: {},
|
||||
response: {}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Force an XPCOM cycle collection. For more information on XPCOM cycle
|
||||
* collection, see
|
||||
* https://developer.mozilla.org/en-US/docs/Interfacing_with_the_XPCOM_cycle_collector#What_the_cycle_collector_does
|
||||
*/
|
||||
forceCycleCollection: method(function() {
|
||||
Cu.forceCC();
|
||||
}, {
|
||||
request: {},
|
||||
response: {}
|
||||
}),
|
||||
|
||||
/**
|
||||
* A method that returns a detailed breakdown of the memory consumption of the
|
||||
* associated window.
|
||||
|
@ -74,8 +74,13 @@ skip-if = buildapp == 'mulet'
|
||||
[test_inspector_getImageData.html]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[test_memory.html]
|
||||
[test_memory_allocations_01.html]
|
||||
[test_memory_allocations_02.html]
|
||||
[test_memory_allocations_03.html]
|
||||
[test_memory_attach_01.html]
|
||||
[test_memory_attach_02.html]
|
||||
[test_memory_census.html]
|
||||
[test_memory_gc_01.html]
|
||||
[test_preference.html]
|
||||
[test_connectToChild.html]
|
||||
skip-if = buildapp == 'mulet'
|
||||
|
@ -52,3 +52,9 @@ function destroyServerAndFinish(client) {
|
||||
SimpleTest.finish()
|
||||
});
|
||||
}
|
||||
|
||||
function waitForTime(ms) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 1067491 - Test recording allocations.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Memory monitoring actor test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
var { memory, client } = yield startServerAndGetSelectedTabMemory();
|
||||
yield memory.attach();
|
||||
|
||||
yield memory.startRecordingAllocations();
|
||||
ok(true, "Can start recording allocations");
|
||||
|
||||
// Allocate some objects.
|
||||
|
||||
var alloc1, alloc2, alloc3;
|
||||
(function outer() {
|
||||
(function middle() {
|
||||
(function inner() {
|
||||
alloc1 = {}; alloc1.line = Error().lineNumber;
|
||||
alloc2 = []; alloc2.line = Error().lineNumber;
|
||||
alloc3 = new function() {}; alloc3.line = Error().lineNumber;
|
||||
}());
|
||||
}());
|
||||
}());
|
||||
|
||||
var response = yield memory.getAllocations();
|
||||
|
||||
yield memory.stopRecordingAllocations();
|
||||
ok(true, "Can stop recording allocations");
|
||||
|
||||
// Filter out allocations by library and test code, and get only the
|
||||
// allocations that occurred in our test case above.
|
||||
|
||||
function isTestAllocation(alloc) {
|
||||
var frame = response.frames[alloc];
|
||||
return frame.functionDisplayName === "inner"
|
||||
&& (frame.line === alloc1.line
|
||||
|| frame.line === alloc2.line
|
||||
|| frame.line === alloc3.line);
|
||||
}
|
||||
|
||||
var testAllocations = response.allocations.filter(isTestAllocation);
|
||||
ok(testAllocations.length >= 3,
|
||||
"Should find our 3 test allocations (plus some allocations for the error "
|
||||
+ "objects used to get line numbers)");
|
||||
|
||||
// For each of the test case's allocations, ensure that the parent frame
|
||||
// indices are correct. Also test that we did get an allocation at each
|
||||
// line we expected (rather than a bunch on the first line and none on the
|
||||
// others, etc).
|
||||
|
||||
var expectedLines = new Set([alloc1.line, alloc2.line, alloc3.line]);
|
||||
|
||||
for (var alloc of testAllocations) {
|
||||
var innerFrame = response.frames[alloc];
|
||||
ok(innerFrame, "Should get the inner frame");
|
||||
is(innerFrame.functionDisplayName, "inner");
|
||||
expectedLines.delete(innerFrame.line);
|
||||
|
||||
var middleFrame = response.frames[innerFrame.parent];
|
||||
ok(middleFrame, "Should get the middle frame");
|
||||
is(middleFrame.functionDisplayName, "middle");
|
||||
|
||||
var outerFrame = response.frames[middleFrame.parent];
|
||||
ok(outerFrame, "Should get the outer frame");
|
||||
is(outerFrame.functionDisplayName, "outer");
|
||||
|
||||
// Not going to test the rest of the frames because they are Task.jsm
|
||||
// and promise frames and it gets gross. Plus, I wouldn't want this test
|
||||
// to start failing if they changed their implementations in a way that
|
||||
// added or removed stack frames here.
|
||||
}
|
||||
|
||||
is(expectedLines.size, 0,
|
||||
"Should have found all the expected lines");
|
||||
|
||||
yield memory.detach();
|
||||
destroyServerAndFinish(client);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,64 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 1067491 - Test aggregating allocation counts.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Memory monitoring actor test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
var { memory, client } = yield startServerAndGetSelectedTabMemory();
|
||||
yield memory.attach();
|
||||
|
||||
yield memory.startRecordingAllocations();
|
||||
ok(true, "Can start recording allocations");
|
||||
|
||||
// Allocate some objects.
|
||||
|
||||
var allocs = [];
|
||||
(function allocator() {
|
||||
for (var i = 0; i < 10; i++) {
|
||||
allocs.push({});
|
||||
}
|
||||
}());
|
||||
|
||||
var response = yield memory.getAllocations();
|
||||
|
||||
yield memory.stopRecordingAllocations();
|
||||
ok(true, "Can stop recording allocations");
|
||||
|
||||
// Find the index of our 10 allocations, and then assert that it is in the
|
||||
// `allocator` frame.
|
||||
|
||||
var index = 0;
|
||||
var found = false;
|
||||
for (var count of response.counts) {
|
||||
if (count === 10) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
ok(found, "Should find the 10 allocations.");
|
||||
|
||||
is(response.frames[index].functionDisplayName, "allocator",
|
||||
"Should have found the allocator frame.");
|
||||
|
||||
yield memory.detach();
|
||||
destroyServerAndFinish(client);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,78 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 1067491 - Test that frames keep the same index while we are recording.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Memory monitoring actor test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
var { memory, client } = yield startServerAndGetSelectedTabMemory();
|
||||
yield memory.attach();
|
||||
|
||||
yield memory.startRecordingAllocations();
|
||||
|
||||
// Allocate twice with the exact same stack (hence setTimeout rather than
|
||||
// allocating directly in the generator), but with getAllocations() calls in
|
||||
// between.
|
||||
|
||||
var allocs = [];
|
||||
function allocator() {
|
||||
allocs.push({});
|
||||
}
|
||||
|
||||
setTimeout(allocator, 1);
|
||||
yield waitForTime(2);
|
||||
var first = yield memory.getAllocations();
|
||||
|
||||
setTimeout(allocator, 1);
|
||||
yield waitForTime(2);
|
||||
var second = yield memory.getAllocations();
|
||||
|
||||
yield memory.stopRecordingAllocations();
|
||||
|
||||
// Assert that each frame in the first response has the same index in the
|
||||
// second response. This isn't commutative, so we don't check that all
|
||||
// of the second response's frames are the same in the first response,
|
||||
// because there might be new allocations that happen after the first query
|
||||
// but before the second.
|
||||
|
||||
function assertSameFrame(a, b) {
|
||||
info("Checking frames at index " + i + ":");
|
||||
info(" First frame = " + JSON.stringify(a, null, 4));
|
||||
info(" Second frame = " + JSON.stringify(b, null, 4));
|
||||
|
||||
is(!!a, !!b);
|
||||
if (!a || !b) {
|
||||
return;
|
||||
}
|
||||
|
||||
is(a.source, b.source);
|
||||
is(a.line, b.line);
|
||||
is(a.column, b.column);
|
||||
is(a.functionDisplayName, b.functionDisplayName);
|
||||
is(a.parent, b.parent);
|
||||
}
|
||||
|
||||
for (var i = 0; i < first.frames.length; i++) {
|
||||
assertSameFrame(first.frames[i], second.frames[i]);
|
||||
}
|
||||
|
||||
yield memory.detach();
|
||||
destroyServerAndFinish(client);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 1067491 - Test taking a census over the RDP.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Memory monitoring actor test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
var { memory, client } = yield startServerAndGetSelectedTabMemory();
|
||||
yield memory.attach();
|
||||
|
||||
var census = yield memory.takeCensus();
|
||||
is(typeof census, "object");
|
||||
|
||||
yield memory.detach();
|
||||
destroyServerAndFinish(client);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 1067491 - Test forcing a gc.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Memory monitoring actor test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
var { memory, client } = yield startServerAndGetSelectedTabMemory();
|
||||
|
||||
var objects = [];
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var o = {};
|
||||
o[Math.random] = 1;
|
||||
objects.push(o);
|
||||
}
|
||||
|
||||
objects = null;
|
||||
var { total: beforeGC } = yield memory.measure();
|
||||
|
||||
yield memory.forceGarbageCollection();
|
||||
var { total: afterGC } = yield memory.measure();
|
||||
|
||||
ok(beforeGC > afterGC);
|
||||
|
||||
destroyServerAndFinish(client);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user