summaryrefslogtreecommitdiffstats
path: root/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/aboutmemory/tests/test_memoryReporters.xul')
-rw-r--r--toolkit/components/aboutmemory/tests/test_memoryReporters.xul424
1 files changed, 424 insertions, 0 deletions
diff --git a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
new file mode 100644
index 000000000..9d56890b3
--- /dev/null
+++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
@@ -0,0 +1,424 @@
+<?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>