Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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"/>
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. -->
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>
19 <!-- test code goes here -->
20 <script type="application/javascript">
21 <![CDATA[
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.
27 "use strict";
29 const Cc = Components.classes;
30 const Ci = Components.interfaces;
31 const Cr = Components.results;
33 const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
34 const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
35 const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
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;
42 let vsizeAmounts = [];
43 let residentAmounts = [];
44 let jsGcHeapAmounts = [];
45 let heapAllocatedAmounts = [];
46 let storageSqliteAmounts = [];
48 let present = {}
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);
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 }
67 let mySandbox = Components.utils.Sandbox(document.nodePrincipal,
68 { sandboxName: "this-is-a-sandbox-name" });
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);
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 }
110 let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
111 getService(Ci.nsIMemoryReporterManager);
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 }
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);
174 mgr.getReportsForThisProcess(handleReport, null);
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 }
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);
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");
206 // Reporter registration tests
208 // collectReports() calls to the test reporter.
209 let called = 0;
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 };
290 // General memory reporter + registerStrongReporter tests.
291 function test_register_strong() {
292 let reporterAndCallback = new MemoryReporterAndCallback();
293 // Registration works.
294 mgr.registerStrongReporter(reporterAndCallback);
296 // Check the generated reports.
297 mgr.getReportsForThisProcess(reporterAndCallback, null);
298 reporterAndCallback.finish();
300 // Unregistration works.
301 mgr.unregisterStrongReporter(reporterAndCallback);
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 }
309 test_register_strong();
311 // Check strong reporters a second time, to make sure a reporter can be
312 // re-registered.
313 test_register_strong();
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 }
337 test_register_weak();
339 ]]>
340 </script>
341 </window>