========================================
Tutorial: Show Allocations Per Call Path
========================================
.. |br| raw:: html
This page shows how to use the :doc:`Debugger API <../index>` to show how many objects a web page allocates, sorted by the function call path that allocated them.
1. Visit the URL ``about:config``, and set the ``devtools.chrome.enabled`` preference to ``true``:
.. image:: enable-chrome-devtools.png
:alt: Setting the devtools.chrome.enabled preference
:class: center
Setting the ``devtools.chrome.enabled`` preference
|br|
2. Open a developer Scratchpad (Menu button > Developer > Scratchpad), and select "Browser" from the "Environment" menu. (This menu will not be present unless you have changed the preference as explained above.)
.. image:: scratchpad-browser-environment.png
:alt: Selecting the browser context in the Scratchpad
:class: center
Selecting the 'browser' context in the Scratchpad
|br|
3. Enter the following code in the Scratchpad:
.. code-block:: javascript
// This defines the 'Debugger' constructor in this
// Scratchpad; it doesn't actually start debugging anything.
const { addDebuggerToGlobal } = ChromeUtils.importESModule(
'resource://gre/modules/jsdebugger.sys.mjs'
);
addDebuggerToGlobal(window);
(function () {
// The debugger we'll use to observe a tab's allocation.
var dbg;
// Start measuring the selected tab's main window's memory
// consumption. This function is available in the browser
// console.
window.demoTrackAllocations = function() {
dbg = new Debugger;
// This makes hacking on the demo *much* more
// pleasant.
dbg.uncaughtExceptionHook = handleUncaughtException;
// Find the current tab's main content window.
var w = gBrowser.selectedBrowser.contentWindow;
console.log("Tracking allocations in page: " +
w.location.href);
// Make that window a debuggee of our Debugger.
dbg.addDebuggee(w.wrappedJSObject);
// Enable allocation tracking in dbg's debuggees.
dbg.memory.trackingAllocationSites = true;
}
window.demoPlotAllocations = function() {
// Grab the allocation log.
var log = dbg.memory.drainAllocationsLog();
// Neutralize the Debugger, and drop it on the floor
// for the GC to collect.
console.log("Stopping allocation tracking.");
dbg.removeAllDebuggees();
dbg = undefined;
// Analyze and display the allocation log.
plot(log);
}
function handleUncaughtException(ex) {
console.log('Debugger hook threw:');
console.log(ex.toString());
console.log('Stack:');
console.log(ex.stack);
};
function plot(log) {
// Given the log, compute a map from allocation sites to
// allocation counts. Note that stack entries are '===' if
// they represent the same site with the same callers.
var counts = new Map;
for (let site of log) {
// This is a kludge, necessary for now. The saved stacks
// are new, and Firefox doesn't yet understand that they
// are safe for chrome code to use, so we must tell it
// so explicitly.
site = Components.utils.waiveXrays(site.frame);
if (!counts.has(site))
counts.set(site, 0);
counts.set(site, counts.get(site) + 1);
}
// Walk from each site that allocated something up to the
// root, computing allocation totals that include
// children. Remember that 'null' is a valid site,
// representing the root.
var totals = new Map;
for (let [site, count] of counts) {
for(;;) {
if (!totals.has(site))
totals.set(site, 0);
totals.set(site, totals.get(site) + count);
if (!site)
break;
site = site.parent;
}
}
// Compute parent-to-child links, since saved stack frames
// have only parent links.
var rootChildren = new Map;
function childMapFor(site) {
if (!site)
return rootChildren;
let parentMap = childMapFor(site.parent);
if (parentMap.has(site))
return parentMap.get(site);
var m = new Map;
parentMap.set(site, m);
return m;
}
for (let [site, total] of totals) {
childMapFor(site);
}
// Print the allocation count for |site|. Print
// |children|'s entries as |site|'s child nodes. Indent
// the whole thing by |indent|.
function walk(site, children, indent) {
var name, place;
if (site) {
name = site.functionDisplayName;
place = ' ' + site.source + ':' + site.line + ':' + site.column;
} else {
name = '(root)';
place = '';
}
console.log(indent + totals.get(site) + ': ' + name + place);
for (let [child, grandchildren] of children)
walk(child, grandchildren, indent + ' ');
}
walk(null, rootChildren, '');
}
})();
|br|
4. In the Scratchpad, ensure that no text is selected, and press the "Run" button. (If you get an error complaining that ``Components.utils`` is not defined, be sure you've selected ``Browser`` from the scratchpad's ``Environment`` menu, as described in step 2.)
|br|
5. Save the following HTML text to a file, and visit the file in your browser. Make sure the current browser tab is displaying this page.
.. code-block:: html