|
1 <?xml version="1.0"?> |
|
2 <?xml-stylesheet type="text/css" href="chrome://global/skin"?> |
|
3 <?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> |
|
4 <window title="Memory reporters" |
|
5 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> |
|
6 <script type="application/javascript" |
|
7 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> |
|
8 |
|
9 <!-- This file tests (in a rough fashion) whether the memory reporters are |
|
10 producing sensible results. test_aboutmemory.xul tests the |
|
11 presentation of memory reports in about:memory. --> |
|
12 |
|
13 <!-- test results are displayed in the html:body --> |
|
14 <body xmlns="http://www.w3.org/1999/xhtml"> |
|
15 <!-- In bug 773533, <marquee> elements crashed the JS memory reporter --> |
|
16 <marquee>Marquee</marquee> |
|
17 </body> |
|
18 |
|
19 <!-- test code goes here --> |
|
20 <script type="application/javascript"> |
|
21 <![CDATA[ |
|
22 |
|
23 // Nb: this test is all JS and so should be done with an xpcshell test, |
|
24 // but bug 671753 is preventing the memory-reporter-manager from being |
|
25 // accessed from xpcshell. |
|
26 |
|
27 "use strict"; |
|
28 |
|
29 const Cc = Components.classes; |
|
30 const Ci = Components.interfaces; |
|
31 const Cr = Components.results; |
|
32 |
|
33 const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP; |
|
34 const HEAP = Ci.nsIMemoryReporter.KIND_HEAP; |
|
35 const OTHER = Ci.nsIMemoryReporter.KIND_OTHER; |
|
36 |
|
37 const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES; |
|
38 const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT; |
|
39 const COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE; |
|
40 const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE; |
|
41 |
|
42 let vsizeAmounts = []; |
|
43 let residentAmounts = []; |
|
44 let jsGcHeapAmounts = []; |
|
45 let heapAllocatedAmounts = []; |
|
46 let storageSqliteAmounts = []; |
|
47 |
|
48 let present = {} |
|
49 |
|
50 // Generate a long, random string. We'll check that this string is |
|
51 // reported in at least one of the memory reporters. |
|
52 let bigString = ""; |
|
53 while (bigString.length < 10000) { |
|
54 bigString += Math.random(); |
|
55 } |
|
56 let bigStringPrefix = bigString.substring(0, 100); |
|
57 |
|
58 // Generate many copies of two distinctive short strings, "!)(*&" and |
|
59 // "@)(*&". We'll check that these strings are reported in at least |
|
60 // one of the memory reporters. |
|
61 let shortStrings = []; |
|
62 for (let i = 0; i < 10000; i++) { |
|
63 let str = (Math.random() > 0.5 ? "!" : "@") + ")(*&"; |
|
64 shortStrings.push(str); |
|
65 } |
|
66 |
|
67 let mySandbox = Components.utils.Sandbox(document.nodePrincipal, |
|
68 { sandboxName: "this-is-a-sandbox-name" }); |
|
69 |
|
70 function handleReport(aProcess, aPath, aKind, aUnits, aAmount, aDescription) |
|
71 { |
|
72 // Record the values of some notable reporters. |
|
73 if (aPath === "vsize") { |
|
74 vsizeAmounts.push(aAmount); |
|
75 } else if (aPath === "resident") { |
|
76 residentAmounts.push(aAmount); |
|
77 } else if (aPath === "js-main-runtime-gc-heap-committed/used/gc-things") { |
|
78 jsGcHeapAmounts.push(aAmount); |
|
79 } else if (aPath === "heap-allocated") { |
|
80 heapAllocatedAmounts.push(aAmount); |
|
81 } else if (aPath === "storage-sqlite") { |
|
82 storageSqliteAmounts.push(aAmount); |
|
83 |
|
84 // Check the presence of some other notable reporters. |
|
85 } else if (aPath.search(/^explicit\/js-non-window\/.*compartment\(/) >= 0) { |
|
86 present.jsNonWindowCompartments = true; |
|
87 } else if (aPath.search(/^explicit\/window-objects\/top\(.*\/js-compartment\(/) >= 0) { |
|
88 present.windowObjectsJsCompartments = true; |
|
89 } else if (aPath.search(/^explicit\/storage\/sqlite\/places.sqlite/) >= 0) { |
|
90 present.places = true; |
|
91 } else if (aPath.search(/^explicit\/images/) >= 0) { |
|
92 present.images = true; |
|
93 } else if (aPath.search(/^explicit\/xpti-working-set$/) >= 0) { |
|
94 present.xptiWorkingSet = true; |
|
95 } else if (aPath.search(/^explicit\/atom-tables$/) >= 0) { |
|
96 present.atomTable = true; |
|
97 } else if (/\[System Principal\].*this-is-a-sandbox-name/.test(aPath)) { |
|
98 // A system compartment with a location (such as a sandbox) should |
|
99 // show that location. |
|
100 present.sandboxLocation = true; |
|
101 } else if (aPath.contains(bigStringPrefix)) { |
|
102 present.bigString = true; |
|
103 } else if (aPath.contains("!)(*&")) { |
|
104 present.smallString1 = true; |
|
105 } else if (aPath.contains("@)(*&")) { |
|
106 present.smallString2 = true; |
|
107 } |
|
108 } |
|
109 |
|
110 let mgr = Cc["@mozilla.org/memory-reporter-manager;1"]. |
|
111 getService(Ci.nsIMemoryReporterManager); |
|
112 |
|
113 // Access the distinguished amounts (mgr.explicit et al.) just to make sure |
|
114 // they don't crash. We can't check their actual values because they're |
|
115 // non-deterministic. |
|
116 // |
|
117 // Nb: mgr.explicit will throw NS_ERROR_NOT_AVAILABLE if this is a |
|
118 // --enable-trace-malloc build. Allow for that exception, but *only* that |
|
119 // exception. |
|
120 let dummy; |
|
121 let haveExplicit = true; |
|
122 try { |
|
123 dummy = mgr.explicit; |
|
124 } catch (ex) { |
|
125 is(ex.result, Cr.NS_ERROR_NOT_AVAILABLE, "mgr.explicit exception"); |
|
126 haveExplicit = false; |
|
127 } |
|
128 let amounts = [ |
|
129 "vsize", |
|
130 "vsizeMaxContiguous", |
|
131 "resident", |
|
132 "residentFast", |
|
133 "heapAllocated", |
|
134 "heapOverheadRatio", |
|
135 "JSMainRuntimeGCHeap", |
|
136 "JSMainRuntimeTemporaryPeak", |
|
137 "JSMainRuntimeCompartmentsSystem", |
|
138 "JSMainRuntimeCompartmentsUser", |
|
139 "imagesContentUsedUncompressed", |
|
140 "storageSQLite", |
|
141 "lowMemoryEventsVirtual", |
|
142 "lowMemoryEventsPhysical", |
|
143 "ghostWindows", |
|
144 "pageFaultsHard", |
|
145 ]; |
|
146 for (let i = 0; i < amounts.length; i++) { |
|
147 try { |
|
148 // If mgr[amounts[i]] throws an exception, just move on -- some amounts |
|
149 // aren't available on all platforms. But if the attribute simply |
|
150 // isn't present, that indicates the distinguished amounts have changed |
|
151 // and this file hasn't been updated appropriately. |
|
152 dummy = mgr[amounts[i]]; |
|
153 ok(dummy !== undefined, |
|
154 "accessed an unknown distinguished amount: " + amounts[i]); |
|
155 } catch (ex) { |
|
156 } |
|
157 } |
|
158 |
|
159 // Run sizeOfTab() to make sure it doesn't crash. We can't check the result |
|
160 // values because they're non-deterministic. |
|
161 let jsObjectsSize = {}; |
|
162 let jsStringsSize = {}; |
|
163 let jsOtherSize = {}; |
|
164 let domSize = {}; |
|
165 let styleSize = {}; |
|
166 let otherSize = {}; |
|
167 let totalSize = {}; |
|
168 let jsMilliseconds = {}; |
|
169 let nonJSMilliseconds = {}; |
|
170 mgr.sizeOfTab(window, jsObjectsSize, jsStringsSize, jsOtherSize, |
|
171 domSize, styleSize, otherSize, totalSize, |
|
172 jsMilliseconds, nonJSMilliseconds); |
|
173 |
|
174 mgr.getReportsForThisProcess(handleReport, null); |
|
175 |
|
176 function checkSpecialReport(aName, aAmounts, aCanBeUnreasonable) |
|
177 { |
|
178 ok(aAmounts.length == 1, aName + " has " + aAmounts.length + " report"); |
|
179 let n = aAmounts[0]; |
|
180 // Check the size is reasonable -- i.e. not ridiculously large or small. |
|
181 ok((100 * 1000 <= n && n <= 10 * 1000 * 1000 * 1000) || aCanBeUnreasonable, |
|
182 aName + "'s size is reasonable"); |
|
183 } |
|
184 |
|
185 // If mgr.explicit failed, we won't have "heap-allocated" either. |
|
186 if (haveExplicit) { |
|
187 checkSpecialReport("heap-allocated", heapAllocatedAmounts); |
|
188 } |
|
189 // vsize may be unreasonable if ASAN is enabled |
|
190 checkSpecialReport("vsize", vsizeAmounts, /*canBeUnreasonable*/true); |
|
191 checkSpecialReport("resident", residentAmounts); |
|
192 checkSpecialReport("js-main-runtime-gc-heap-committed/used/gc-things", jsGcHeapAmounts); |
|
193 |
|
194 ok(present.jsNonWindowCompartments, "js-non-window compartments are present"); |
|
195 ok(present.windowObjectsJsCompartments, "window-objects/.../js compartments are present"); |
|
196 ok(present.places, "places is present"); |
|
197 ok(present.images, "images is present"); |
|
198 ok(present.xptiWorkingSet, "xpti-working-set is present"); |
|
199 ok(present.atomTable, "atom-table is present"); |
|
200 ok(present.sandboxLocation, "sandbox locations are present"); |
|
201 ok(present.bigString, "large string is present"); |
|
202 ok(present.smallString1, "small string 1 is present"); |
|
203 ok(present.smallString2, "small string 2 is present"); |
|
204 |
|
205 |
|
206 // Reporter registration tests |
|
207 |
|
208 // collectReports() calls to the test reporter. |
|
209 let called = 0; |
|
210 |
|
211 // The test memory reporter, testing the various report units. |
|
212 // Also acts as a report collector, verifying the reported values match the |
|
213 // expected ones after passing through XPConnect / nsMemoryReporterManager |
|
214 // and back. |
|
215 function MemoryReporterAndCallback() { |
|
216 this.seen = 0; |
|
217 } |
|
218 MemoryReporterAndCallback.prototype = { |
|
219 // The test reports. |
|
220 // Each test key corresponds to the path of the report. |amount| is a |
|
221 // function called when generating the report. |expected| is a function |
|
222 // to be tested when receiving a report during collection. If |expected| is |
|
223 // omitted the |amount| will be checked instead. |
|
224 tests: { |
|
225 "test-memory-reporter-bytes1": { |
|
226 units: BYTES, |
|
227 amount: () => 0 |
|
228 }, |
|
229 "test-memory-reporter-bytes2": { |
|
230 units: BYTES, |
|
231 amount: () => (1<<30) * 8 // awkward way to say 8G in JS |
|
232 }, |
|
233 "test-memory-reporter-counter": { |
|
234 units: COUNT, |
|
235 amount: () => 2 |
|
236 }, |
|
237 "test-memory-reporter-ccounter": { |
|
238 units: COUNT_CUMULATIVE, |
|
239 amount: () => ++called, |
|
240 expected: () => called |
|
241 }, |
|
242 "test-memory-reporter-percentage": { |
|
243 units: PERCENTAGE, |
|
244 amount: () => 9999 |
|
245 } |
|
246 }, |
|
247 // nsIMemoryReporter |
|
248 collectReports: function(callback, data) { |
|
249 for (let path of Object.keys(this.tests)) { |
|
250 try { |
|
251 let test = this.tests[path]; |
|
252 callback.callback( |
|
253 "", // Process. Should be "" initially. |
|
254 path, |
|
255 OTHER, |
|
256 test.units, |
|
257 test.amount(), |
|
258 "Test " + path + ".", |
|
259 data); |
|
260 } |
|
261 catch (ex) { |
|
262 ok(false, ex); |
|
263 } |
|
264 } |
|
265 }, |
|
266 // nsIMemoryReporterCallback |
|
267 callback: function(process, path, kind, units, amount, data) { |
|
268 if (path in this.tests) { |
|
269 this.seen++; |
|
270 let test = this.tests[path]; |
|
271 ok(units === test.units, "Test reporter units match"); |
|
272 ok(amount === (test.expected || test.amount)(), |
|
273 "Test reporter values match: " + amount); |
|
274 } |
|
275 }, |
|
276 // Checks that the callback has seen the expected number of reports, and |
|
277 // resets the callback counter. |
|
278 // @param expected Optional. Expected number of reports the callback |
|
279 // should have processed. |
|
280 finish: function(expected) { |
|
281 if (expected === undefined) { |
|
282 expected = Object.keys(this.tests).length; |
|
283 } |
|
284 is(expected, this.seen, |
|
285 "Test reporter called the correct number of times: " + expected); |
|
286 this.seen = 0; |
|
287 } |
|
288 }; |
|
289 |
|
290 // General memory reporter + registerStrongReporter tests. |
|
291 function test_register_strong() { |
|
292 let reporterAndCallback = new MemoryReporterAndCallback(); |
|
293 // Registration works. |
|
294 mgr.registerStrongReporter(reporterAndCallback); |
|
295 |
|
296 // Check the generated reports. |
|
297 mgr.getReportsForThisProcess(reporterAndCallback, null); |
|
298 reporterAndCallback.finish(); |
|
299 |
|
300 // Unregistration works. |
|
301 mgr.unregisterStrongReporter(reporterAndCallback); |
|
302 |
|
303 // The reporter was unregistered, hence there shouldn't be any reports from |
|
304 // the test reporter. |
|
305 mgr.getReportsForThisProcess(reporterAndCallback, null); |
|
306 reporterAndCallback.finish(0); |
|
307 } |
|
308 |
|
309 test_register_strong(); |
|
310 |
|
311 // Check strong reporters a second time, to make sure a reporter can be |
|
312 // re-registered. |
|
313 test_register_strong(); |
|
314 |
|
315 |
|
316 // Check that you cannot register JS components as weak reporters. |
|
317 function test_register_weak() { |
|
318 let reporterAndCallback = new MemoryReporterAndCallback(); |
|
319 try { |
|
320 // Should fail! nsMemoryReporterManager will only hold a raw pointer to |
|
321 // "weak" reporters. When registering a weak reporter, XPConnect will |
|
322 // create a WrappedJS for JS components. This WrappedJS would be |
|
323 // successfully registered with the manager, only to be destroyed |
|
324 // immediately after, which would eventually lead to a crash when |
|
325 // collecting the reports. Therefore nsMemoryReporterManager should |
|
326 // reject WrappedJS reporters, which is what is tested here. |
|
327 // See bug 950391 comment #0. |
|
328 mgr.registerWeakReporter(reporterAndCallback); |
|
329 ok(false, "Shouldn't be allowed to register a JS component (WrappedJS)"); |
|
330 } |
|
331 catch (ex) { |
|
332 ok(ex.message.indexOf("NS_ERROR_") >= 0, |
|
333 "WrappedJS reporter got rejected: " + ex); |
|
334 } |
|
335 } |
|
336 |
|
337 test_register_weak(); |
|
338 |
|
339 ]]> |
|
340 </script> |
|
341 </window> |
|
342 |