mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1200853 - Utility function for mapping census data into a tree view. r=fitzgen
This commit is contained in:
parent
ac09b20a06
commit
b5b21cd6f8
87
browser/devtools/memory/modules/census.js
Normal file
87
browser/devtools/memory/modules/census.js
Normal file
@ -0,0 +1,87 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Utilities for interfacing with census reports from dbg.memory.takeCensus().
|
||||
*/
|
||||
|
||||
const COARSE_TYPES = new Set(["objects", "scripts", "strings", "other"]);
|
||||
|
||||
/**
|
||||
* Takes a report from a census (`dbg.memory.takeCensus()`) and the breakdown
|
||||
* used to generate the census and returns a structure used to render
|
||||
* a tree to display the data.
|
||||
*
|
||||
* Returns a recursive "CensusTreeNode" object, looking like:
|
||||
*
|
||||
* CensusTreeNode = {
|
||||
* // `children` if it exists, is sorted by `bytes`, if they are leaf nodes.
|
||||
* children: ?[<CensusTreeNode...>],
|
||||
* name: <?String>
|
||||
* count: <?Number>
|
||||
* bytes: <?Number>
|
||||
* }
|
||||
*
|
||||
* @param {Object} breakdown
|
||||
* @param {Object} report
|
||||
* @param {?String} name
|
||||
* @return {Object}
|
||||
*/
|
||||
function CensusTreeNode (breakdown, report, name) {
|
||||
this.name = name;
|
||||
this.bytes = void 0;
|
||||
this.count = void 0;
|
||||
this.children = void 0;
|
||||
|
||||
CensusTreeNodeBreakdowns[breakdown.by](this, breakdown, report);
|
||||
|
||||
if (this.children) {
|
||||
this.children.sort(sortByBytes);
|
||||
}
|
||||
}
|
||||
|
||||
CensusTreeNode.prototype = null;
|
||||
|
||||
/**
|
||||
* A series of functions to handle different breakdowns used by CensusTreeNode
|
||||
*/
|
||||
const CensusTreeNodeBreakdowns = Object.create(null);
|
||||
|
||||
CensusTreeNodeBreakdowns.count = function (node, breakdown, report) {
|
||||
if (breakdown.bytes === true) {
|
||||
node.bytes = report.bytes;
|
||||
}
|
||||
if (breakdown.count === true) {
|
||||
node.count = report.count;
|
||||
}
|
||||
};
|
||||
|
||||
CensusTreeNodeBreakdowns.internalType = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
for (let key of Object.keys(report)) {
|
||||
node.children.push(new CensusTreeNode(breakdown.then, report[key], key));
|
||||
}
|
||||
}
|
||||
|
||||
CensusTreeNodeBreakdowns.objectClass = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
for (let key of Object.keys(report)) {
|
||||
let bd = key === "other" ? breakdown.other : breakdown.then;
|
||||
node.children.push(new CensusTreeNode(bd, report[key], key));
|
||||
}
|
||||
}
|
||||
|
||||
CensusTreeNodeBreakdowns.coarseType = function (node, breakdown, report) {
|
||||
node.children = [];
|
||||
for (let type of Object.keys(breakdown).filter(type => COARSE_TYPES.has(type))) {
|
||||
node.children.push(new CensusTreeNode(breakdown[type], report[type], type));
|
||||
}
|
||||
}
|
||||
|
||||
function sortByBytes (a, b) {
|
||||
return (b.bytes || 0) - (a.bytes || 0);
|
||||
}
|
||||
|
||||
exports.CensusTreeNode = CensusTreeNode;
|
10
browser/devtools/memory/moz.build
Normal file
10
browser/devtools/memory/moz.build
Normal file
@ -0,0 +1,10 @@
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_JS_MODULES.devtools.memory += [
|
||||
'modules/census.js',
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
145
browser/devtools/memory/test/unit/head.js
Normal file
145
browser/devtools/memory/test/unit/head.js
Normal file
@ -0,0 +1,145 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
const CC = Components.Constructor;
|
||||
|
||||
const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
|
||||
const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
const { CensusTreeNode } = require("devtools/memory/census");
|
||||
const Services = require("Services");
|
||||
|
||||
// Always log packets when running tests. runxpcshelltests.py will throw
|
||||
// the output away anyway, unless you give it the --verbose flag.
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
|
||||
const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"]
|
||||
.createInstance(Ci.nsIPrincipal);
|
||||
|
||||
function dumpn(msg) {
|
||||
dump("HEAPSNAPSHOT-TEST: " + msg + "\n");
|
||||
}
|
||||
|
||||
function addTestingFunctionsToGlobal(global) {
|
||||
global.eval(
|
||||
`
|
||||
const testingFunctions = Components.utils.getJSTestingFunctions();
|
||||
for (let k in testingFunctions) {
|
||||
this[k] = testingFunctions[k];
|
||||
}
|
||||
`
|
||||
);
|
||||
if (!global.print) {
|
||||
global.print = do_print;
|
||||
}
|
||||
if (!global.newGlobal) {
|
||||
global.newGlobal = newGlobal;
|
||||
}
|
||||
if (!global.Debugger) {
|
||||
addDebuggerToGlobal(global);
|
||||
}
|
||||
}
|
||||
|
||||
addTestingFunctionsToGlobal(this);
|
||||
|
||||
/**
|
||||
* Create a new global, with all the JS shell testing functions. Similar to the
|
||||
* newGlobal function exposed to JS shells, and useful for porting JS shell
|
||||
* tests to xpcshell tests.
|
||||
*/
|
||||
function newGlobal() {
|
||||
const global = new Cu.Sandbox(SYSTEM_PRINCIPAL, { freshZone: true });
|
||||
addTestingFunctionsToGlobal(global);
|
||||
return global;
|
||||
}
|
||||
|
||||
function assertThrowsValue(f, val, msg) {
|
||||
var fullmsg;
|
||||
try {
|
||||
f();
|
||||
} catch (exc) {
|
||||
if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
|
||||
return;
|
||||
fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
|
||||
}
|
||||
if (fullmsg === undefined)
|
||||
fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
|
||||
if (msg !== undefined)
|
||||
fullmsg += " - " + msg;
|
||||
throw new Error(fullmsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path of the file with the specified name in a
|
||||
* platform-independent and URL-like form.
|
||||
*/
|
||||
function getFilePath(aName, aAllowMissing=false, aUsePlatformPathSeparator=false)
|
||||
{
|
||||
let file = do_get_file(aName, aAllowMissing);
|
||||
let path = Services.io.newFileURI(file).spec;
|
||||
let filePrePath = "file://";
|
||||
if ("nsILocalFileWin" in Ci &&
|
||||
file instanceof Ci.nsILocalFileWin) {
|
||||
filePrePath += "/";
|
||||
}
|
||||
|
||||
path = path.slice(filePrePath.length);
|
||||
|
||||
if (aUsePlatformPathSeparator && path.match(/^\w:/)) {
|
||||
path = path.replace(/\//g, "\\");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a heap snapshot to the file with the given name in the current
|
||||
* directory, read it back as a HeapSnapshot instance, and then take a census of
|
||||
* the heap snapshot's serialized heap graph with the provided census options.
|
||||
*
|
||||
* @param {Object|undefined} censusOptions
|
||||
* Options that should be passed through to the takeCensus method. See
|
||||
* js/src/doc/Debugger/Debugger.Memory.md for details.
|
||||
*
|
||||
* @param {Debugger|null} dbg
|
||||
* If a Debugger object is given, only serialize the subgraph covered by
|
||||
* the Debugger's debuggees. If null, serialize the whole heap graph.
|
||||
*
|
||||
* @param {String} fileName
|
||||
* The file name to save the heap snapshot's core dump file to, within
|
||||
* the current directory.
|
||||
*
|
||||
* @returns Census
|
||||
*/
|
||||
function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined,
|
||||
// Add the Math.random() so that parallel
|
||||
// tests are less likely to mess with
|
||||
// each other.
|
||||
fileName="core-dump-" + (Math.random()) + ".tmp") {
|
||||
const filePath = getFilePath(fileName, true, true);
|
||||
ok(filePath, "Should get a file path to save the core dump to.");
|
||||
|
||||
const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
|
||||
ChromeUtils.saveHeapSnapshot(filePath, snapshotOptions);
|
||||
ok(true, "Should have saved a heap snapshot to " + filePath);
|
||||
|
||||
const snapshot = ChromeUtils.readHeapSnapshot(filePath);
|
||||
ok(snapshot, "Should have read a heap snapshot back from " + filePath);
|
||||
ok(snapshot instanceof HeapSnapshot, "snapshot should be an instance of HeapSnapshot");
|
||||
|
||||
equal(typeof snapshot.takeCensus, "function", "snapshot should have a takeCensus method");
|
||||
return snapshot.takeCensus(censusOptions);
|
||||
}
|
||||
|
||||
function compareCensusViewData (breakdown, report, expected, assertion) {
|
||||
let data = new CensusTreeNode(breakdown, report);
|
||||
console.log(data);
|
||||
console.log(expected);
|
||||
equal(JSON.stringify(data), JSON.stringify(expected), assertion);
|
||||
}
|
37
browser/devtools/memory/test/unit/test_census-01.js
Normal file
37
browser/devtools/memory/test/unit/test_census-01.js
Normal file
@ -0,0 +1,37 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests CensusTreeNode with `internalType` breakdown.
|
||||
*/
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
|
||||
}
|
||||
|
||||
const BREAKDOWN = {
|
||||
by: "internalType",
|
||||
then: { by: "count", count: true, bytes: true }
|
||||
};
|
||||
|
||||
const REPORT = {
|
||||
"JSObject": {
|
||||
"bytes": 100,
|
||||
"count": 10,
|
||||
},
|
||||
"js::Shape": {
|
||||
"bytes": 500,
|
||||
"count": 50,
|
||||
},
|
||||
"JSString": {
|
||||
"bytes": 0,
|
||||
"count": 0,
|
||||
},
|
||||
};
|
||||
|
||||
const EXPECTED = {
|
||||
children: [
|
||||
{ name: "js::Shape", bytes: 500, count: 50, },
|
||||
{ name: "JSObject", bytes: 100, count: 10, },
|
||||
{ name: "JSString", bytes: 0, count: 0, },
|
||||
],
|
||||
};
|
45
browser/devtools/memory/test/unit/test_census-02.js
Normal file
45
browser/devtools/memory/test/unit/test_census-02.js
Normal file
@ -0,0 +1,45 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests CensusTreeNode with `coarseType` breakdown.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
|
||||
}
|
||||
|
||||
const countBreakdown = { by: "count", count: true, bytes: true };
|
||||
|
||||
const BREAKDOWN = {
|
||||
by: "coarseType",
|
||||
objects: { by: "objectClass", then: countBreakdown },
|
||||
strings: countBreakdown,
|
||||
other: { by: "internalType", then: countBreakdown },
|
||||
};
|
||||
|
||||
const REPORT = {
|
||||
"objects": {
|
||||
"Function": { bytes: 10, count: 1 },
|
||||
"Array": { bytes: 20, count: 2 },
|
||||
},
|
||||
"strings": { bytes: 10, count: 1 },
|
||||
"other": {
|
||||
"js::Shape": { bytes: 30, count: 3 },
|
||||
"js::Shape2": { bytes: 40, count: 4 }
|
||||
},
|
||||
};
|
||||
|
||||
const EXPECTED = {
|
||||
children: [
|
||||
{ name: "strings", bytes: 10, count: 1, },
|
||||
{ name: "objects", children: [
|
||||
{ name: "Array", bytes: 20, count: 2, },
|
||||
{ name: "Function", bytes: 10, count: 1, },
|
||||
]},
|
||||
{ name: "other", children: [
|
||||
{ name: "js::Shape2", bytes: 40, count: 4, },
|
||||
{ name: "js::Shape", bytes: 30, count: 3, },
|
||||
]},
|
||||
]
|
||||
};
|
38
browser/devtools/memory/test/unit/test_census-03.js
Normal file
38
browser/devtools/memory/test/unit/test_census-03.js
Normal file
@ -0,0 +1,38 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests CensusTreeNode with `objectClass` breakdown.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, `${JSON.stringify(BREAKDOWN)} has correct results.`);
|
||||
}
|
||||
|
||||
const countBreakdown = { by: "count", count: true, bytes: true };
|
||||
|
||||
const BREAKDOWN = {
|
||||
by: "objectClass",
|
||||
then: countBreakdown,
|
||||
other: { by: "internalType", then: countBreakdown }
|
||||
};
|
||||
|
||||
const REPORT = {
|
||||
"Function": { bytes: 10, count: 10 },
|
||||
"Array": { bytes: 100, count: 1 },
|
||||
"other": {
|
||||
"JIT::CODE::NOW!!!": { bytes: 20, count: 2 },
|
||||
"JIT::CODE::LATER!!!": { bytes: 40, count: 4 }
|
||||
}
|
||||
};
|
||||
|
||||
const EXPECTED = {
|
||||
children: [
|
||||
{ name: "Array", bytes: 100, count: 1 },
|
||||
{ name: "Function", bytes: 10, count: 10 },
|
||||
{ name: "other", children: [
|
||||
{ name: "JIT::CODE::LATER!!!", bytes: 40, count: 4 },
|
||||
{ name: "JIT::CODE::NOW!!!", bytes: 20, count: 2 },
|
||||
]}
|
||||
]
|
||||
};
|
10
browser/devtools/memory/test/unit/xpcshell.ini
Normal file
10
browser/devtools/memory/test/unit/xpcshell.ini
Normal file
@ -0,0 +1,10 @@
|
||||
[DEFAULT]
|
||||
tags = devtools
|
||||
head = head.js
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
[test_census-01.js]
|
||||
[test_census-02.js]
|
||||
[test_census-03.js]
|
@ -16,6 +16,7 @@ DIRS += [
|
||||
'inspector',
|
||||
'layoutview',
|
||||
'markupview',
|
||||
'memory',
|
||||
'netmonitor',
|
||||
'performance',
|
||||
'projecteditor',
|
||||
|
Loading…
Reference in New Issue
Block a user