<?xml version="1.0"?> <?xml-stylesheet type="text/css" href="chrome://global/skin"?> <?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> <window title="Memory reporters" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> <!-- This file tests (in a rough fashion) whether the memory reporters are producing sensible results. test_aboutmemory.xul tests the presentation of memory reports in about:memory. --> <!-- test results are displayed in the html:body --> <body xmlns="http://www.w3.org/1999/xhtml"> <!-- In bug 773533, <marquee> elements crashed the JS memory reporter --> <marquee>Marquee</marquee> </body> <!-- some URIs that should be anonymized in anonymous mode --> <iframe id="amFrame" height="200" src="http://example.org:80"></iframe> <iframe id="amFrame" height="200" src="https://example.com:443"></iframe> <!-- test code goes here --> <script type="application/javascript"> <![CDATA[ "use strict"; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP; const HEAP = Ci.nsIMemoryReporter.KIND_HEAP; const OTHER = Ci.nsIMemoryReporter.KIND_OTHER; const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES; const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT; const COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE; const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE; // Use backslashes instead of forward slashes due to memory reporting's hacky // handling of URLs. const XUL_NS = "http:\\\\www.mozilla.org\\keymaster\\gatekeeper\\there.is.only.xul"; SimpleTest.waitForExplicitFinish(); let vsizeAmounts = []; let residentAmounts = []; let heapAllocatedAmounts = []; let storageSqliteAmounts = []; let jsGcHeapUsedGcThingsTotal = 0; let jsGcHeapUsedGcThings = {}; let present = {} // Generate a long, random string. We'll check that this string is // reported in at least one of the memory reporters. let bigString = ""; while (bigString.length < 10000) { bigString += Math.random(); } let bigStringPrefix = bigString.substring(0, 100); // Generate many copies of two distinctive short strings, "!)(*&" and // "@)(*&". We'll check that these strings are reported in at least // one of the memory reporters. let shortStrings = []; for (let i = 0; i < 10000; i++) { let str = (Math.random() > 0.5 ? "!" : "@") + ")(*&"; shortStrings.push(str); } let mySandbox = Components.utils.Sandbox(document.nodePrincipal, { sandboxName: "this-is-a-sandbox-name" }); function handleReportNormal(aProcess, aPath, aKind, aUnits, aAmount, aDescription) { // Record the values of some notable reporters. if (aPath === "vsize") { vsizeAmounts.push(aAmount); } else if (aPath === "resident") { residentAmounts.push(aAmount); } else if (aPath.search(/^js-main-runtime-gc-heap-committed\/used\/gc-things\//) >= 0) { jsGcHeapUsedGcThingsTotal += aAmount; jsGcHeapUsedGcThings[aPath] = (jsGcHeapUsedGcThings[aPath] | 0) + 1; } else if (aPath === "heap-allocated") { heapAllocatedAmounts.push(aAmount); } else if (aPath === "storage-sqlite") { storageSqliteAmounts.push(aAmount); // Check the presence of some other notable reporters. } else if (aPath.search(/^explicit\/js-non-window\/.*compartment\(/) >= 0) { present.jsNonWindowCompartments = true; } else if (aPath.search(/^explicit\/window-objects\/top\(.*\/js-compartment\(/) >= 0) { present.windowObjectsJsCompartments = true; } else if (aPath.search(/^explicit\/storage\/sqlite\/places.sqlite/) >= 0) { present.places = true; } else if (aPath.search(/^explicit\/images/) >= 0) { present.images = true; } else if (aPath.search(/^explicit\/xpti-working-set$/) >= 0) { present.xptiWorkingSet = true; } else if (aPath.search(/^explicit\/atom-tables\/main$/) >= 0) { present.atomTablesMain = true; } else if (/\[System Principal\].*this-is-a-sandbox-name/.test(aPath)) { // A system compartment with a location (such as a sandbox) should // show that location. present.sandboxLocation = true; } else if (aPath.includes(bigStringPrefix)) { present.bigString = true; } else if (aPath.includes("!)(*&")) { present.smallString1 = true; } else if (aPath.includes("@)(*&")) { present.smallString2 = true; } // Shouldn't get any anonymized paths. if (aPath.includes('<anonymized')) { present.anonymizedWhenUnnecessary = aPath; } } function handleReportAnonymized(aProcess, aPath, aKind, aUnits, aAmount, aDescription) { // Path might include an xmlns using http, which is safe to ignore. let reducedPath = aPath.replace(XUL_NS, ""); // Shouldn't get http: or https: in any paths. if (reducedPath.includes('http:')) { present.httpWhenAnonymized = aPath; } // file: URLs should have their path anonymized. if (reducedPath.search('file:..[^<]') !== -1) { present.unanonymizedFilePathWhenAnonymized = aPath; } } let mgr = Cc["@mozilla.org/memory-reporter-manager;1"]. getService(Ci.nsIMemoryReporterManager); let amounts = [ "vsize", "vsizeMaxContiguous", "resident", "residentFast", "residentPeak", "residentUnique", "heapAllocated", "heapOverheadFraction", "JSMainRuntimeGCHeap", "JSMainRuntimeTemporaryPeak", "JSMainRuntimeCompartmentsSystem", "JSMainRuntimeCompartmentsUser", "imagesContentUsedUncompressed", "storageSQLite", "lowMemoryEventsVirtual", "lowMemoryEventsPhysical", "ghostWindows", "pageFaultsHard", ]; for (let i = 0; i < amounts.length; i++) { try { // If mgr[amounts[i]] throws an exception, just move on -- some amounts // aren't available on all platforms. But if the attribute simply // isn't present, that indicates the distinguished amounts have changed // and this file hasn't been updated appropriately. let dummy = mgr[amounts[i]]; ok(dummy !== undefined, "accessed an unknown distinguished amount: " + amounts[i]); } catch (ex) { } } // Run sizeOfTab() to make sure it doesn't crash. We can't check the result // values because they're non-deterministic. let jsObjectsSize = {}; let jsStringsSize = {}; let jsOtherSize = {}; let domSize = {}; let styleSize = {}; let otherSize = {}; let totalSize = {}; let jsMilliseconds = {}; let nonJSMilliseconds = {}; mgr.sizeOfTab(window, jsObjectsSize, jsStringsSize, jsOtherSize, domSize, styleSize, otherSize, totalSize, jsMilliseconds, nonJSMilliseconds); let asyncSteps = [ getReportsNormal, getReportsAnonymized, checkResults, test_register_strong, test_register_strong, // Make sure re-registering works test_register_weak, SimpleTest.finish ]; function runNext() { setTimeout(asyncSteps.shift(), 0); } function getReportsNormal() { mgr.getReports(handleReportNormal, null, runNext, null, /* anonymize = */ false); } function getReportsAnonymized() { mgr.getReports(handleReportAnonymized, null, runNext, null, /* anonymize = */ true); } function checkSizeReasonable(aName, aAmount) { // Check the size is reasonable -- i.e. not ridiculously large or small. ok(100 * 1000 <= aAmount && aAmount <= 10 * 1000 * 1000 * 1000, aName + "'s size is reasonable"); } function checkSpecialReport(aName, aAmounts, aCanBeUnreasonable) { ok(aAmounts.length == 1, aName + " has " + aAmounts.length + " report"); let n = aAmounts[0]; if (!aCanBeUnreasonable) { checkSizeReasonable(aName, n); } } function checkResults() { try { // Nb: mgr.heapAllocated will throw NS_ERROR_NOT_AVAILABLE if this is a // --disable-jemalloc build. Allow for skipping this test on that // exception, but *only* that exception. let dummy = mgr.heapAllocated; checkSpecialReport("heap-allocated", heapAllocatedAmounts); } catch (ex) { is(ex.result, Cr.NS_ERROR_NOT_AVAILABLE, "mgr.heapAllocated exception"); } // vsize may be unreasonable if ASAN is enabled checkSpecialReport("vsize", vsizeAmounts, /*canBeUnreasonable*/true); checkSpecialReport("resident", residentAmounts); for (var reporter in jsGcHeapUsedGcThings) { ok(jsGcHeapUsedGcThings[reporter] == 1); } checkSizeReasonable("js-main-runtime-gc-heap-committed/used/gc-things", jsGcHeapUsedGcThingsTotal); ok(present.jsNonWindowCompartments, "js-non-window compartments are present"); ok(present.windowObjectsJsCompartments, "window-objects/.../js compartments are present"); ok(present.places, "places is present"); ok(present.images, "images is present"); ok(present.xptiWorkingSet, "xpti-working-set is present"); ok(present.atomTablesMain, "atom-tables/main is present"); ok(present.sandboxLocation, "sandbox locations are present"); ok(present.bigString, "large string is present"); ok(present.smallString1, "small string 1 is present"); ok(present.smallString2, "small string 2 is present"); ok(!present.anonymizedWhenUnnecessary, "anonymized paths are not present when unnecessary. Failed case: " + present.anonymizedWhenUnnecessary); ok(!present.httpWhenAnonymized, "http URLs are anonymized when necessary. Failed case: " + present.httpWhenAnonymized); ok(!present.unanonymizedFilePathWhenAnonymized, "file URLs are anonymized when necessary. Failed case: " + present.unanonymizedFilePathWhenAnonymized); runNext(); } // Reporter registration tests // collectReports() calls to the test reporter. let called = 0; // The test memory reporter, testing the various report units. // Also acts as a report collector, verifying the reported values match the // expected ones after passing through XPConnect / nsMemoryReporterManager // and back. function MemoryReporterAndCallback() { this.seen = 0; } MemoryReporterAndCallback.prototype = { // The test reports. // Each test key corresponds to the path of the report. |amount| is a // function called when generating the report. |expected| is a function // to be tested when receiving a report during collection. If |expected| is // omitted the |amount| will be checked instead. tests: { "test-memory-reporter-bytes1": { units: BYTES, amount: () => 0 }, "test-memory-reporter-bytes2": { units: BYTES, amount: () => (1<<30) * 8 // awkward way to say 8G in JS }, "test-memory-reporter-counter": { units: COUNT, amount: () => 2 }, "test-memory-reporter-ccounter": { units: COUNT_CUMULATIVE, amount: () => ++called, expected: () => called }, "test-memory-reporter-percentage": { units: PERCENTAGE, amount: () => 9999 } }, // nsIMemoryReporter collectReports: function(callback, data, anonymize) { for (let path of Object.keys(this.tests)) { try { let test = this.tests[path]; callback.callback( "", // Process. Should be "" initially. path, OTHER, test.units, test.amount(), "Test " + path + ".", data); } catch (ex) { ok(false, ex); } } }, // nsIMemoryReporterCallback callback: function(process, path, kind, units, amount, data) { if (path in this.tests) { this.seen++; let test = this.tests[path]; ok(units === test.units, "Test reporter units match"); ok(amount === (test.expected || test.amount)(), "Test reporter values match: " + amount); } }, // Checks that the callback has seen the expected number of reports, and // resets the callback counter. // @param expected Optional. Expected number of reports the callback // should have processed. finish: function(expected) { if (expected === undefined) { expected = Object.keys(this.tests).length; } is(expected, this.seen, "Test reporter called the correct number of times: " + expected); this.seen = 0; } }; // General memory reporter + registerStrongReporter tests. function test_register_strong() { let reporterAndCallback = new MemoryReporterAndCallback(); // Registration works. mgr.registerStrongReporter(reporterAndCallback); // Check the generated reports. mgr.getReports(reporterAndCallback, null, () => { reporterAndCallback.finish(); window.setTimeout(test_unregister_strong, 0, reporterAndCallback); }, null, /* anonymize = */ false); } function test_unregister_strong(aReporterAndCallback) { mgr.unregisterStrongReporter(aReporterAndCallback); // The reporter was unregistered, hence there shouldn't be any reports from // the test reporter. mgr.getReports(aReporterAndCallback, null, () => { aReporterAndCallback.finish(0); runNext(); }, null, /* anonymize = */ false); } // Check that you cannot register JS components as weak reporters. function test_register_weak() { let reporterAndCallback = new MemoryReporterAndCallback(); try { // Should fail! nsMemoryReporterManager will only hold a raw pointer to // "weak" reporters. When registering a weak reporter, XPConnect will // create a WrappedJS for JS components. This WrappedJS would be // successfully registered with the manager, only to be destroyed // immediately after, which would eventually lead to a crash when // collecting the reports. Therefore nsMemoryReporterManager should // reject WrappedJS reporters, which is what is tested here. // See bug 950391 comment #0. mgr.registerWeakReporter(reporterAndCallback); ok(false, "Shouldn't be allowed to register a JS component (WrappedJS)"); } catch (ex) { ok(ex.message.indexOf("NS_ERROR_") >= 0, "WrappedJS reporter got rejected: " + ex); } runNext(); } // Kick-off the async tests. runNext(); ]]> </script> </window>