toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
author Nicholas Nethercote <nnethercote@mozilla.com>
Wed, 17 Sep 2014 21:22:57 -0700
changeset 206127 b453cf797446779dd92d6817b77440f1d9c75b93
parent 203717 3de899a46769ed9d10f8180d23807281f2c28fb8
child 226285 996ff2931452f8748a756dcafdf8e4f05b3903e2
permissions -rw-r--r--
Bug 1059612 - Filter diff-breaking info from memory reports in a more rigorous fashion. r=erahm.

<?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="about:memory"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>

  <!-- This file tests the saving and loading of memory reports to/from file in
       about:memory. -->

  <!-- test results are displayed in the html:body -->
  <body xmlns="http://www.w3.org/1999/xhtml"></body>

  <!-- test code goes here -->
  <script type="application/javascript">
  <![CDATA[
  "use strict";

  const Cc = Components.classes;
  const Ci = Components.interfaces;
  let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
            getService(Ci.nsIMemoryReporterManager);

  // Hide all the real reporters;  we'll restore them at the end.
  mgr.blockRegistrationAndHideExistingReporters();

  // Setup a minimal number of fake reporters.
  const KB = 1024;
  const MB = KB * KB;
  const HEAP  = Ci.nsIMemoryReporter.KIND_HEAP;
  const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
  const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;

  let fakeReporters = [
    { collectReports: function(aCbObj, aClosure, aAnonymize) {
        function f(aP, aK, aA, aD) {
          aCbObj.callback("", aP, aK, BYTES, aA, aD, aClosure);
        }
        f("heap-allocated",     OTHER,   250 * MB, "Heap allocated.");
        f("explicit/a/b",       HEAP,     50 * MB, "A b.");
        f("other/a",            OTHER,   0.2 * MB, "Other a.");
        f("other/b",            OTHER,   0.1 * MB, "Other b.");
      }
    }
  ];

  for (let i = 0; i < fakeReporters.length; i++) {
    mgr.registerStrongReporterEvenIfBlocked(fakeReporters[i]);
  }

  ]]>
  </script>

  <iframe id="amFrame"  height="400" src="about:memory"></iframe>

  <script type="application/javascript">
  <![CDATA[
  function finish()
  {
    mgr.unblockRegistrationAndRestoreOriginalReporters();
    SimpleTest.finish();
  }

  // Load the given file into the frame, then copy+paste the entire frame and
  // check that the cut text matches what we expect.
  function test(aFilename, aFilename2, aExpected, aDumpFirst, aVerbose, aNext) {
    let frame = document.getElementById("amFrame");
    frame.focus();

    let doc = frame.contentWindow.document;
    let verbosity = doc.getElementById("verbose");
    verbosity.checked = aVerbose;

    function getFilePath(aFilename) {
      let file = Cc["@mozilla.org/file/directory_service;1"]
                 .getService(Components.interfaces.nsIProperties)
                 .get("CurWorkD", Components.interfaces.nsIFile);
      file.append("chrome");
      file.append("toolkit");
      file.append("components");
      file.append("aboutmemory");
      file.append("tests");
      file.append(aFilename);
      return file.path;
    }

    let filePath = getFilePath(aFilename);

    let e = document.createEvent('Event');
    e.initEvent('change', true, true);

    function check() {
      // Initialize the clipboard contents.
      SpecialPowers.clipboardCopyString("initial clipboard value");

      let numFailures = 0, maxFailures = 30;

      // Because the file load is async, we don't know when it will finish and
      // the output will show up.  So we poll.
      function copyPasteAndCheck() {
        // Copy and paste frame contents, and filter out non-deterministic
        // differences.
        synthesizeKey("A", {accelKey: true});
        synthesizeKey("C", {accelKey: true});
        let actual = SpecialPowers.getClipboardData("text/unicode");
        actual = actual.replace(/\(pid \d+\)/g, "(pid NNN)");

        if (actual === aExpected) {
          SimpleTest.ok(true, "Clipboard has the expected contents");
          aNext();
        } else {
          numFailures++;
          if (numFailures === maxFailures) {
            ok(false, "pasted text doesn't match");
            dump("******EXPECTED******\n");
            dump(aExpected);
            dump("*******ACTUAL*******\n");
            dump(actual);
            dump("********************\n");
            finish();
          } else {
            setTimeout(copyPasteAndCheck, 100);
          }
        }
      }
      copyPasteAndCheck();
    }

    if (!aFilename2) {
      function loadAndCheck() {
        let fileInput1 =
          frame.contentWindow.document.getElementById("fileInput1");
        fileInput1.value = filePath;    // this works because it's a chrome test

        fileInput1.dispatchEvent(e);
        check();
      }

      if (aDumpFirst) {
        let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
                        getService(Ci.nsIMemoryInfoDumper);
        dumper.dumpMemoryReportsToNamedFile(filePath, loadAndCheck, null,
                                            /* anonymize = */ false);
      } else {
        loadAndCheck();
      }

    } else {
      let fileInput2 =
        frame.contentWindow.document.getElementById("fileInput2");
      fileInput2.value = filePath;    // this works because it's a chrome test

      // Hack alert:  fileInput2's onchange handler calls fileInput2.click().
      // But we don't want that to happen, because we want to bypass the file
      // picker for the test.  So we set |e.skipClick|, which causes
      // fileInput2.click() to be skipped, and dispatch the second change event
      // directly ourselves.

      e.skipClick = true;
      fileInput2.dispatchEvent(e);

      let filePath2 = getFilePath(aFilename2);
      fileInput2.value = filePath2;    // this works because it's a chrome test

      let e2 = document.createEvent('Event');
      e2.initEvent('change', true, true);
      fileInput2.dispatchEvent(e);

      check();
    }
  }

  // Returns a function that chains together multiple test() calls.
  function chain(aPieces) {
    let x = aPieces.shift();
    if (x) {
      return function() { test(x.filename, x.filename2, x.expected, x.dumpFirst, x.verbose, chain(aPieces)); }
    } else {
      return function() { finish(); };
    }
  }

  let expectedGood =
"\
Explicit-only process\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
100,000 B (100.0%) -- explicit\n\
└──100,000 B (100.0%) ── a/b\n\
\n\
Other Measurements\n\
\n\
End of Explicit-only process\n\
Heap-unclassified process\n\
Explicit Allocations\n\
\n\
262,144,000 B (100.0%) -- explicit\n\
├──209,715,200 B (80.00%) ── heap-unclassified\n\
└───52,428,800 B (20.00%) ── a/b\n\
\n\
Other Measurements\n\
\n\
262,144,000 B ── heap-allocated\n\
\n\
End of Heap-unclassified process\n\
Main Process (pid NNN)\n\
Explicit Allocations\n\
\n\
262,144,000 B (100.0%) -- explicit\n\
├──209,715,200 B (80.00%) ── heap-unclassified\n\
└───52,428,800 B (20.00%) ── a/b\n\
\n\
Other Measurements\n\
\n\
1,024 B (100.0%) -- compartments\n\
└──1,024 B (100.0%) ── system/a\n\
\n\
1,024 B (100.0%) -- ghost-windows\n\
└──1,024 B (100.0%) ── a\n\
\n\
314,572 B (100.0%) -- other\n\
├──209,715 B (66.67%) ── a\n\
└──104,857 B (33.33%) ── b\n\
\n\
1,024 B (100.0%) -- pss\n\
└──1,024 B (100.0%) ── a\n\
\n\
1,024 B (100.0%) -- rss\n\
└──1,024 B (100.0%) ── a\n\
\n\
1,024 B (100.0%) -- size\n\
└──1,024 B (100.0%) ── a\n\
\n\
1,024 B (100.0%) -- swap\n\
└──1,024 B (100.0%) ── a\n\
\n\
262,144,000 B ── heap-allocated\n\
\n\
End of Main Process (pid NNN)\n\
Other-only process\n\
Other Measurements\n\
\n\
200,000 B (100.0%) -- a\n\
├──100,000 B (50.00%) ── b\n\
└──100,000 B (50.00%) ── c\n\
\n\
500,000 B ── heap-allocated\n\
\n\
End of Other-only process\n\
";

  let expectedGood2 =
"\
Main Process (pid NNN)\n\
Explicit Allocations\n\
\n\
262,144,000 B (100.0%) -- explicit\n\
├──209,715,200 B (80.00%) ── heap-unclassified\n\
└───52,428,800 B (20.00%) ── a/b\n\
\n\
Other Measurements\n\
\n\
314,572 B (100.0%) -- other\n\
├──209,715 B (66.67%) ── a\n\
└──104,857 B (33.33%) ── b\n\
\n\
262,144,000 B ── heap-allocated\n\
\n\
End of Main Process (pid NNN)\n\
";

  // This is the output for a malformed data file.
  let expectedBad =
"\
Invalid memory report(s): missing 'hasMozMallocUsableSize' property\
";

  // This is the output for a non-verbose diff.
  let expectedDiffNonVerbose =
"\
P\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
-0.01 MB (100.0%) -- explicit\n\
├──-0.01 MB (99.95%) ── storage/prefixset/goog-phish-shavar\n\
└──-0.00 MB (00.05%) ++ (2 tiny)\n\
\n\
Other Measurements\n\
\n\
0.96 MB (100.0%) -- a\n\
├──0.95 MB (99.80%) ── b\n\
├──0.00 MB (00.10%) -- c\n\
│  ├──-0.95 MB (-99.70%) ── e\n\
│  ├──0.95 MB (99.60%) ── d\n\
│  └──0.00 MB (00.20%) ++ (2 tiny)\n\
└──0.00 MB (00.10%) ── h\n\
\n\
 0.00 MB ── canvas-2d-pixel-bytes [2] [+]\n\
-0.00 MB ── foobar [-]\n\
\n\
End of P\n\
P2 (pid NNN)\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
0.00 MB (100.0%) -- explicit\n\
└──0.00 MB (100.0%) ── window-objects/top(bar.com, id=NNN)/...\n\
\n\
Other Measurements\n\
\n\
0.00 MB (100.0%) -- p3\n\
└──0.00 MB (100.0%) ── zone(0xNNN)/p3\n\
\n\
0.00 MB (100.0%) -- p4\n\
└──0.00 MB (100.0%) ── js-zone(0xNNN)/p4\n\
\n\
0.00 MB (100.0%) -- p5\n\
└──0.00 MB (100.0%) ── worker(foo.com, 0xNNN)/p5\n\
\n\
0.00 MB (100.0%) -- p6\n\
└──0.00 MB (100.0%) ── z-moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}/p6\n\
\n\
0.00 MB ── p1 (pid NNN)\n\
0.00 MB ── p2 (blah, pid=NNN)\n\
\n\
End of P2 (pid NNN)\n\
P3\n\
Other Measurements\n\
\n\
-0.00 MB ── p3 [-]\n\
\n\
End of P3\n\
P4\n\
Other Measurements\n\
\n\
0.00 MB ── p4 [+]\n\
\n\
End of P4\n\
P7\n\
Other Measurements\n\
\n\
0.00 MB (100.0%) -- p7\n\
├──0.00 MB (57.14%) ── c [+]\n\
└──0.00 MB (42.86%) ── b [+]\n\
\n\
-0.00 MB ── p7 [-]\n\
\n\
End of P7\n\
P8\n\
Other Measurements\n\
\n\
-0.00 MB (100.0%) -- p8\n\
└──-0.00 MB (100.0%) -- a\n\
   ├──-0.00 MB (50.00%) -- b\n\
   │  ├──-0.00 MB (31.82%) -- c\n\
   │  │  ├──-0.00 MB (18.18%) ── e [-]\n\
   │  │  └──-0.00 MB (13.64%) ── d [-]\n\
   │  ├──-0.00 MB (22.73%) ── f [-]\n\
   │  └───0.00 MB (-4.55%) ── (fake child) [!]\n\
   └──-0.00 MB (50.00%) -- g\n\
      ├──-0.00 MB (31.82%) ── i [-]\n\
      ├──-0.00 MB (27.27%) ── h [-]\n\
      └───0.00 MB (-9.09%) ── (fake child) [!]\n\
\n\
End of P8\n\
";

  // This is the output for a verbose diff.
  let expectedDiffVerbose =
"\
P\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
-10,005 B (100.0%) -- explicit\n\
├──-10,000 B (99.95%) ── storage/prefixset/goog-phish-shavar\n\
├───────-6 B (00.06%) ── spell-check [2]\n\
└────────1 B (-0.01%) ── xpcom/category-manager\n\
\n\
Other Measurements\n\
\n\
1,002,000 B (100.0%) -- a\n\
├──1,000,000 B (99.80%) ── b\n\
├──────1,000 B (00.10%) -- c\n\
│      ├──-999,000 B (-99.70%) ── e\n\
│      ├──998,000 B (99.60%) ── d\n\
│      ├──1,000 B (00.10%) ── f\n\
│      └──1,000 B (00.10%) ── g\n\
└──────1,000 B (00.10%) ── h\n\
\n\
3,000 B ── canvas-2d-pixel-bytes [2] [+]\n\
 -100 B ── foobar [-]\n\
\n\
End of P\n\
P2 (pid NNN)\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
11 B (100.0%) -- explicit\n\
└──11 B (100.0%) ── window-objects/top(bar.com, id=NNN)/...\n\
\n\
Other Measurements\n\
\n\
11 B (100.0%) -- p3\n\
└──11 B (100.0%) ── zone(0xNNN)/p3\n\
\n\
11 B (100.0%) -- p4\n\
└──11 B (100.0%) ── js-zone(0xNNN)/p4\n\
\n\
11 B (100.0%) -- p5\n\
└──11 B (100.0%) ── worker(foo.com, 0xNNN)/p5\n\
\n\
11 B (100.0%) -- p6\n\
└──11 B (100.0%) ── z-moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}/p6\n\
\n\
11 B ── p1 (pid NNN)\n\
11 B ── p2 (blah, pid=NNN)\n\
\n\
End of P2 (pid NNN)\n\
P3\n\
Other Measurements\n\
\n\
-55 B ── p3 [-]\n\
\n\
End of P3\n\
P4\n\
Other Measurements\n\
\n\
66 B ── p4 [+]\n\
\n\
End of P4\n\
P7\n\
Other Measurements\n\
\n\
7 B (100.0%) -- p7\n\
├──4 B (57.14%) ── c [+]\n\
└──3 B (42.86%) ── b [+]\n\
\n\
-5 B ── p7 [-]\n\
\n\
End of P7\n\
P8\n\
Other Measurements\n\
\n\
-22 B (100.0%) -- p8\n\
└──-22 B (100.0%) -- a\n\
   ├──-11 B (50.00%) -- b\n\
   │  ├───-7 B (31.82%) -- c\n\
   │  │   ├──-4 B (18.18%) ── e [-]\n\
   │  │   └──-3 B (13.64%) ── d [-]\n\
   │  ├───-5 B (22.73%) ── f [-]\n\
   │  └────1 B (-4.55%) ── (fake child) [!]\n\
   └──-11 B (50.00%) -- g\n\
      ├───-7 B (31.82%) ── i [-]\n\
      ├───-6 B (27.27%) ── h [-]\n\
      └────2 B (-9.09%) ── (fake child) [!]\n\
\n\
End of P8\n\
";

  // This is the output for the crash reports diff.
  let expectedDiff2 =
"\
Main Process (pid NNN)\n\
Other Measurements\n\
\n\
1 B ── heap-allocated\n\
\n\
End of Main Process (pid NNN)\n\
";

  let frames = [
    // This loads a pre-existing memory reports file that is valid.
    { filename: "memory-reports-good.json", expected: expectedGood, dumpFirst: false, verbose: true },

    // This loads a pre-existing crash dump file that is valid.
    { filename: "crash-dump-good.json", expected: expectedGood2, dumpFirst: false, verbose: true },

    // This dumps to a file and then reads it back in. (The result is the same
    // as the previous test.)
    { filename: "memory-reports-dumped.json.gz", expected: expectedGood2, dumpFirst: true, verbose: true },

    // This loads a pre-existing file that is invalid.
    { filename: "memory-reports-bad.json",  expected: expectedBad, dumpFirst: false, verbose: true },

    // This diffs two pre-existing memory reports files.
    { filename: "memory-reports-diff1.json", filename2: "memory-reports-diff2.json", expected: expectedDiffNonVerbose, dumpFirst: false, verbose: false },

    // Ditto.
    { filename: "memory-reports-diff1.json", filename2: "memory-reports-diff2.json", expected: expectedDiffVerbose, dumpFirst: false, verbose: true },

    // This diffs two pre-existing crash report files.
    { filename: "crash-dump-diff1.json", filename2: "crash-dump-diff2.json", expected: expectedDiff2, dumpFirst: false, verbose: true }
  ];

  SimpleTest.waitForFocus(chain(frames));

  SimpleTest.waitForExplicitFinish();
  ]]>
  </script>
</window>