1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
|
/* this source code form is subject to the terms of the mozilla public
* license, v. 2.0. if a copy of the mpl was not distributed with this
* file, you can obtain one at http://mozilla.org/mpl/2.0/. */
/*
* This file helps tracking Javascript object allocations.
* It is only included in local builds as a debugging helper.
*
* It is typicaly used when running DevTools tests (either mochitests or DAMP).
* To use it, you need to set the following environment variable:
* DEBUG_DEVTOOLS_ALLOCATIONS="normal"
* This will only print the number of JS objects created during your test.
* DEBUG_DEVTOOLS_ALLOCATIONS="verbose"
* This will print the allocation sites of all the JS objects created during your
* test. i.e. from which files and lines the objects have been created.
* In both cases, look for "DEVTOOLS ALLOCATION" in your terminal to see tracker's
* output.
*
* But you can also import it from your test script if you want to focus on one
* particular piece of code:
* const { allocationTracker } =
* require("devtools/shared/test-helpers/allocation-tracker");
* // Calling `allocationTracker` will immediately start recording allocations
* let tracker = allocationTracker();
*
* // Do something
*
* // If you want to log all the allocation sites, call this method:
* tracker.logAllocationSites();
* // Or, if you want to only print the number of objects being allocated, call this:
* tracker.logCount();
* // Once you are done, stop the tracker as it slow down execution a lot.
* tracker.stop();
*/
"use strict";
const { Cu } = require("chrome");
const ChromeUtils = require("ChromeUtils");
const global = Cu.getGlobalForObject(this);
const { addDebuggerToGlobal } = ChromeUtils.import(
"resource://gre/modules/jsdebugger.jsm"
);
addDebuggerToGlobal(global);
/**
* Start recording JS object allocations.
*
* @param Object watchGlobal
* One global object to observe. Only allocation made from this global
* will be recorded.
* @param Boolean watchAllGlobals
* If true, allocations from everywhere are going to be recorded.
* @param Boolean watchAllGlobals
* If true, only allocations made from DevTools contexts are going to be recorded.
*/
exports.allocationTracker = function({
watchGlobal,
watchAllGlobals,
watchDevToolsGlobals,
} = {}) {
dump("DEVTOOLS ALLOCATION: Start logging allocations\n");
let dbg = new global.Debugger();
// Enable allocation site tracking, to have the stack for each allocation
dbg.memory.trackingAllocationSites = true;
// Force saving *all* the allocation sites
dbg.memory.allocationSamplingProbability = 1.0;
// Bumps the default buffer size, which may prevent recording all the test allocations
dbg.memory.maxAllocationsLogLength = 5000000;
let acceptGlobal;
if (watchGlobal) {
acceptGlobal = () => false;
dbg.addDebuggee(watchGlobal);
} else if (watchAllGlobals) {
acceptGlobal = () => true;
} else if (watchDevToolsGlobals) {
// Only accept globals related to DevTools
acceptGlobal = g => {
// self-hosting-global crashes when trying to call unsafeDereference
if (g.class == "self-hosting-global") {
return false;
}
const ref = g.unsafeDereference();
const location = Cu.getRealmLocation(ref);
const accept = !!location.match(/devtools/i);
dump(
"TRACKER NEW GLOBAL: " + (accept ? "+" : "-") + " : " + location + "\n"
);
return accept;
};
}
// Watch all globals
if (watchAllGlobals || watchDevToolsGlobals) {
dbg.addAllGlobalsAsDebuggees();
for (const g of dbg.getDebuggees()) {
if (!acceptGlobal(g)) {
dbg.removeDebuggee(g);
}
}
}
// Remove this global to ignore all its object/JS
dbg.removeDebuggee(global);
// addAllGlobalsAsDebuggees won't automatically track new ones,
// so ensure tracking all new globals
dbg.onNewGlobalObject = function(g) {
if (acceptGlobal(g)) {
dbg.addDebuggee(g);
}
};
return {
get overflowed() {
return dbg.memory.allocationsLogOverflowed;
},
/**
* Print to stdout data about all recorded allocations
*
* It prints an array of allocations per file, sorted by files allocating the most
* objects. And get detail of allocation per line.
*
* [{ src: "chrome://devtools/content/framework/toolbox.js",
* count: 210, // Total # of allocs for toolbox.js
* lines: [
* "10: 200", // toolbox.js allocation 200 objects on line 10
* "124: 10
* ]
* },
* { src: "chrome://devtools/content/inspector/inspector.js",
* count: 12,
* lines: [
* "20: 12",
* ]
* }]
*
* @param first Number
* Retrieve only the top $first script allocation the most
* objects
*/
logAllocationSites({ first = 5 } = {}) {
// Fetch all allocation sites from Debugger API
const allocations = dbg.memory.drainAllocationsLog();
// Process Debugger API data to store allocations by file
// sources = {
// "chrome://devtools/content/framework/toolbox.js": {
// count: 10, // total # of allocs for toolbox.js
// lines: {
// 10: 200, // total # of allocs for toolbox.js line 10
// 124: 10, // same, for line 124
// ..
// }
// }
// }
const sources = {};
for (const alloc of allocations) {
const { frame } = alloc;
let src = "UNKNOWN";
let line = -1;
try {
if (frame) {
src = frame.source || "UNKNOWN";
line = frame.line || -1;
}
} catch (e) {
// For some frames accessing source throws
}
let item = sources[src];
if (!item) {
item = sources[src] = { count: 0, lines: {} };
}
item.count++;
if (line != -1) {
if (!item.lines[line]) {
item.lines[line] = 0;
}
item.lines[line]++;
}
}
const allocationList = Object.entries(sources)
// Sort by number of total object
.sort(([srcA, itemA], [srcB, itemB]) => itemA.count < itemB.count)
// Keep only the first 5 sources, with the most allocations
.filter((_, i) => i < first)
.map(([src, item]) => {
const lines = [];
Object.entries(item.lines)
.filter(([line, count]) => count > 5)
.sort(([lineA, countA], [lineB, countB]) => {
if (countA != countB) {
return countA < countB;
}
return lineA < lineB;
})
.forEach(([line, count]) => {
// Compress the data to make it readable on stdout
lines.push(line + ": " + count);
});
return { src, count: item.count, lines };
});
dump(
"DEVTOOLS ALLOCATION: Javascript object allocations: " +
allocations.length +
"\n" +
JSON.stringify(allocationList, null, 2) +
"\n"
);
},
logCount() {
dump(
"DEVTOOLS ALLOCATION: Javascript object allocations: " +
this.countAllocations() +
"\n"
);
},
countAllocations() {
// Fetch all allocation sites from Debugger API
const allocations = dbg.memory.drainAllocationsLog();
return allocations.length;
},
flushAllocations() {
dbg.memory.drainAllocationsLog();
},
stillAllocatedObjects() {
const sensus = dbg.memory.takeCensus({ breakdown: { by: "count" } });
return sensus.count;
},
stop() {
dump("DEVTOOLS ALLOCATION: Stop logging allocations\n");
dbg.onNewGlobalObject = undefined;
dbg.removeAllDebuggees();
dbg = null;
},
};
};
|