browser_allocation_tracker.js (7854B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Load the tracker from a distinct compartment so that it can inspect any other 7 // module/compartment, even DevTools, chrome, and this script! 8 const { 9 useDistinctSystemPrincipalLoader, 10 releaseDistinctSystemPrincipalLoader, 11 } = ChromeUtils.importESModule( 12 "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs", 13 { global: "shared" } 14 ); 15 16 const requester = {}; 17 const loader = useDistinctSystemPrincipalLoader(requester); 18 registerCleanupFunction(() => releaseDistinctSystemPrincipalLoader(requester)); 19 20 const { allocationTracker } = loader.require( 21 "chrome://mochitests/content/browser/devtools/shared/test-helpers/allocation-tracker" 22 ); 23 const TrackedObjects = loader.require( 24 "resource://devtools/shared/test-helpers/tracked-objects.sys.mjs" 25 ); 26 27 // This test record multiple times complete heap snapshot, 28 // so that it can take a little bit to complete. 29 requestLongerTimeout(2); 30 31 add_task(async function () { 32 // Use a sandbox to allocate test javascript object in order to avoid any 33 // external noise 34 const global = Cu.Sandbox("http://example.com"); 35 36 const tracker = allocationTracker({ watchGlobal: global }); 37 const before = tracker.stillAllocatedObjects(); 38 39 /* eslint-disable no-undef */ 40 // This will allocation 1001 objects. The array and 1000 elements in it. 41 Cu.evalInSandbox( 42 "let list; new " + 43 function () { 44 list = []; 45 for (let i = 0; i < 1000; i++) { 46 list.push({}); 47 } 48 }, 49 global, 50 undefined, 51 "test-file.js", 52 1, 53 /* enforceFilenameRestrictions */ false 54 ); 55 /* eslint-enable no-undef */ 56 57 const allocations = tracker.countAllocations(); 58 Assert.greaterOrEqual( 59 allocations, 60 1001, 61 `At least 1001 objects are reported as created (${allocations})` 62 ); 63 64 // Uncomment this and comment the call to `countAllocations` to debug the allocations. 65 // The call to `countAllocations` will reset the allocation record. 66 // tracker.logAllocationSites(); 67 68 const afterCreation = tracker.stillAllocatedObjects(); 69 is( 70 afterCreation.objectsWithStack - before.objectsWithStack, 71 1001, 72 "We got exactly the expected number of objects recorded with an allocation site" 73 ); 74 Assert.greater( 75 afterCreation.objectsWithStack, 76 before.objectsWithStack, 77 "We got some random number of objects without an allocation site" 78 ); 79 80 Cu.evalInSandbox( 81 "list = null;", 82 global, 83 undefined, 84 "test-file.js", 85 7, 86 /* enforceFilenameRestrictions */ false 87 ); 88 89 Cu.forceGC(); 90 Cu.forceCC(); 91 92 const afterGC = tracker.stillAllocatedObjects(); 93 is( 94 afterCreation.objectsWithStack - afterGC.objectsWithStack, 95 1001, 96 "All the expected objects were reported freed in the count with allocation sites" 97 ); 98 Assert.less( 99 afterGC.objectsWithoutStack, 100 afterCreation.objectsWithoutStack, 101 "And we released some random number of objects without an allocation site" 102 ); 103 104 tracker.stop(); 105 }); 106 107 add_task(async function () { 108 const leaked = {}; 109 TrackedObjects.track(leaked); 110 let transient = {}; 111 TrackedObjects.track(transient); 112 113 is( 114 TrackedObjects.getStillAllocatedObjects().length, 115 2, 116 "The two objects are reported" 117 ); 118 119 info("Free the transient object"); 120 transient = null; 121 Cu.forceGC(); 122 123 is( 124 TrackedObjects.getStillAllocatedObjects().length, 125 1, 126 "We now only have the leaked object" 127 ); 128 is( 129 TrackedObjects.getStillAllocatedObjects()[0].weakRef.get(), 130 leaked, 131 "The still allocated objects is the leaked one" 132 ); 133 TrackedObjects.clear(); 134 }); 135 136 add_task(async function () { 137 info("Test start and stop recording without any debug mode"); 138 const tracker = allocationTracker({ watchDevToolsGlobals: true }); 139 await tracker.startRecordingAllocations(); 140 await tracker.stopRecordingAllocations(); 141 tracker.stop(); 142 }); 143 144 add_task(async function () { 145 info("Test start and stop recording with 'allocations' debug mode"); 146 const tracker = allocationTracker({ watchDevToolsGlobals: true }); 147 await tracker.startRecordingAllocations("allocations"); 148 await tracker.stopRecordingAllocations("allocations"); 149 tracker.stop(); 150 }); 151 152 add_task(async function () { 153 info("Test start and stop recording with 'leaks' debug mode"); 154 const tracker = allocationTracker({ watchDevToolsGlobals: true }); 155 await tracker.startRecordingAllocations("leaks"); 156 await tracker.stopRecordingAllocations("leaks"); 157 tracker.stop(); 158 }); 159 160 add_task(async function () { 161 info("Test start and stop recording with tracked objects"); 162 163 const leaked = {}; 164 TrackedObjects.track(leaked); 165 166 const tracker = allocationTracker({ watchAllGlobals: true }); 167 await tracker.startRecordingAllocations(); 168 await tracker.stopRecordingAllocations(); 169 tracker.stop(); 170 171 TrackedObjects.clear(); 172 }); 173 174 add_task(async function () { 175 info("Test start and stop recording with tracked objects"); 176 177 const sandbox = Cu.Sandbox(window); 178 const tracker = allocationTracker({ watchGlobal: sandbox }); 179 await tracker.startRecordingAllocations("leaks"); 180 181 Cu.evalInSandbox("this.foo = {};", sandbox, null, "sandbox.js", 1); 182 183 const record = await tracker.stopRecordingAllocations("leaks"); 184 is( 185 record.objectsWithStack, 186 1, 187 "We get only one leaked objects, the foo object of the sandbox." 188 ); 189 Assert.greater( 190 record.objectsWithoutStack, 191 10, 192 "We get an handful of objects without stacks. Most likely created by Memory API itself." 193 ); 194 195 is( 196 record.leaks.length, 197 2, 198 "We get the one leak and the objects with missing stacks" 199 ); 200 is( 201 record.leaks[0].src, 202 "UNKNOWN", 203 "First item is the objects with missing stacks" 204 ); 205 // In theory the two following values should be equal, 206 // but they aren't always because of some dark matter around objects with missing stacks. 207 // `count` is computed out of `takeCensus`, while `objectsWithoutStack` uses `drainAllocationsLog` 208 // While the first go through the current GC graph, the second is a record of allocations over time, 209 // this probably explain why there is some subtle difference 210 Assert.lessOrEqual( 211 record.leaks[0].count, 212 record.objectsWithoutStack, 213 "For now, the leak report intermittently assume there is less leaked objects than the summary" 214 ); 215 is(record.leaks[1].src, "sandbox.js", "Second item if about our 'foo' leak"); 216 is(record.leaks[1].count, 1, "We leak one object on this file"); 217 is(record.leaks[1].lines.length, 1, "We leak from only one line"); 218 is(record.leaks[1].lines[0], "1: 1", "On first line, we leak one object"); 219 tracker.stop(); 220 221 TrackedObjects.clear(); 222 }); 223 224 add_task(async function () { 225 info("Test that transient globals are not leaked"); 226 227 const tracker = allocationTracker({ watchAllGlobals: true }); 228 229 let sandboxBefore = Cu.Sandbox(window); 230 // We need to allocate at least one object from the global to reproduce the leak 231 Cu.evalInSandbox( 232 "this.foo = {};", 233 sandboxBefore, 234 null, 235 "sandbox-before.js", 236 1 237 ); 238 const weakBefore = Cu.getWeakReference(sandboxBefore); 239 sandboxBefore = null; 240 241 await tracker.startRecordingAllocations(); 242 243 ok( 244 !weakBefore.get(), 245 "Sandbox created before the record should have been freed by GCs done by startRecordingAllocations" 246 ); 247 248 let sandboxDuring = Cu.Sandbox(window); 249 // We need to allocate at least one object from the global to reproduce the leak 250 Cu.evalInSandbox( 251 "this.bar = {};", 252 sandboxDuring, 253 null, 254 "sandbox-during.js", 255 1 256 ); 257 const weakDuring = Cu.getWeakReference(sandboxDuring); 258 sandboxDuring = null; 259 260 await tracker.stopRecordingAllocations(); 261 262 ok( 263 !weakDuring.get(), 264 "Sandbox should have been freed by GCs done by stopRecordingAllocations" 265 ); 266 267 tracker.stop(); 268 });