index.rst (8698B)
1 ======================================== 2 Tutorial: Show Allocations Per Call Path 3 ======================================== 4 5 .. |br| raw:: html 6 7 <br/> 8 9 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. 10 11 1. Visit the URL ``about:config``, and set the ``devtools.chrome.enabled`` preference to ``true``: 12 13 .. image:: enable-chrome-devtools.png 14 :alt: Setting the devtools.chrome.enabled preference 15 :class: center 16 17 Setting the ``devtools.chrome.enabled`` preference 18 19 |br| 20 21 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.) 22 23 .. image:: scratchpad-browser-environment.png 24 :alt: Selecting the browser context in the Scratchpad 25 :class: center 26 27 Selecting the 'browser' context in the Scratchpad 28 29 |br| 30 31 3. Enter the following code in the Scratchpad: 32 33 .. code-block:: javascript 34 35 // This defines the 'Debugger' constructor in this 36 // Scratchpad; it doesn't actually start debugging anything. 37 const { addDebuggerToGlobal } = ChromeUtils.importESModule( 38 'resource://gre/modules/jsdebugger.sys.mjs' 39 ); 40 addDebuggerToGlobal(window); 41 42 (function () { 43 // The debugger we'll use to observe a tab's allocation. 44 var dbg; 45 46 // Start measuring the selected tab's main window's memory 47 // consumption. This function is available in the browser 48 // console. 49 window.demoTrackAllocations = function() { 50 dbg = new Debugger; 51 52 // This makes hacking on the demo *much* more 53 // pleasant. 54 dbg.uncaughtExceptionHook = handleUncaughtException; 55 56 // Find the current tab's main content window. 57 var w = gBrowser.selectedBrowser.contentWindow; 58 console.log("Tracking allocations in page: " + 59 w.location.href); 60 61 // Make that window a debuggee of our Debugger. 62 dbg.addDebuggee(w.wrappedJSObject); 63 64 // Enable allocation tracking in dbg's debuggees. 65 dbg.memory.trackingAllocationSites = true; 66 } 67 68 window.demoPlotAllocations = function() { 69 // Grab the allocation log. 70 var log = dbg.memory.drainAllocationsLog(); 71 72 // Neutralize the Debugger, and drop it on the floor 73 // for the GC to collect. 74 console.log("Stopping allocation tracking."); 75 dbg.removeAllDebuggees(); 76 dbg = undefined; 77 78 // Analyze and display the allocation log. 79 plot(log); 80 } 81 82 function handleUncaughtException(ex) { 83 console.log('Debugger hook threw:'); 84 console.log(ex.toString()); 85 console.log('Stack:'); 86 console.log(ex.stack); 87 }; 88 89 function plot(log) { 90 // Given the log, compute a map from allocation sites to 91 // allocation counts. Note that stack entries are '===' if 92 // they represent the same site with the same callers. 93 var counts = new Map; 94 for (let site of log) { 95 // This is a kludge, necessary for now. The saved stacks 96 // are new, and Firefox doesn't yet understand that they 97 // are safe for chrome code to use, so we must tell it 98 // so explicitly. 99 site = Components.utils.waiveXrays(site.frame); 100 101 if (!counts.has(site)) 102 counts.set(site, 0); 103 counts.set(site, counts.get(site) + 1); 104 } 105 106 // Walk from each site that allocated something up to the 107 // root, computing allocation totals that include 108 // children. Remember that 'null' is a valid site, 109 // representing the root. 110 var totals = new Map; 111 for (let [site, count] of counts) { 112 for(;;) { 113 if (!totals.has(site)) 114 totals.set(site, 0); 115 totals.set(site, totals.get(site) + count); 116 if (!site) 117 break; 118 site = site.parent; 119 } 120 } 121 122 // Compute parent-to-child links, since saved stack frames 123 // have only parent links. 124 var rootChildren = new Map; 125 function childMapFor(site) { 126 if (!site) 127 return rootChildren; 128 129 let parentMap = childMapFor(site.parent); 130 if (parentMap.has(site)) 131 return parentMap.get(site); 132 133 var m = new Map; 134 parentMap.set(site, m); 135 return m; 136 } 137 for (let [site, total] of totals) { 138 childMapFor(site); 139 } 140 141 // Print the allocation count for |site|. Print 142 // |children|'s entries as |site|'s child nodes. Indent 143 // the whole thing by |indent|. 144 function walk(site, children, indent) { 145 var name, place; 146 if (site) { 147 name = site.functionDisplayName; 148 place = ' ' + site.source + ':' + site.line + ':' + site.column; 149 } else { 150 name = '(root)'; 151 place = ''; 152 } 153 console.log(indent + totals.get(site) + ': ' + name + place); 154 for (let [child, grandchildren] of children) 155 walk(child, grandchildren, indent + ' '); 156 } 157 walk(null, rootChildren, ''); 158 } 159 })(); 160 161 |br| 162 163 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.) 164 165 |br| 166 167 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. 168 169 .. code-block:: html 170 171 <div onclick="doDivsAndSpans()"> 172 Click here to make the page do some allocations. 173 </div> 174 175 <script> 176 function makeFactory(type) { 177 return function factory(content) { 178 var elt = document.createElement(type); 179 elt.textContent = content; 180 return elt; 181 }; 182 } 183 184 var divFactory = makeFactory('div'); 185 var spanFactory = makeFactory('span'); 186 187 function divsAndSpans() { 188 for (i = 0; i < 10; i++) { 189 var div = divFactory('div #' + i); 190 div.appendChild(spanFactory('span #' + i)); 191 document.body.appendChild(div); 192 } 193 } 194 195 function doDivsAndSpans() { divsAndSpans(); } 196 </script> 197 198 |br| 199 200 6. Open the browser console (Menu Button > Developer > Browser Console), and then evaluate the expression ``demoTrackAllocations()`` in the browser console. This begins logging allocations in the current browser tab. 201 202 |br| 203 204 7. In the browser tab, click on the text that says "Click hereā¦". The event handler should add some text to the end of the page. 205 206 |br| 207 208 8. Back in the browser console, evaluate the expression ``demoPlotAllocations()``. This stops logging allocations, and displays a tree of allocations: 209 210 .. image:: alloc-plot-console.png 211 :alt: An allocation plot, displayed in the console 212 :class: center 213 214 An allocation plot, displayed in the console 215 216 The numbers at the left edge of each line show the total number of objects allocated at that site or at sites called from there. After the count, we see the function name, and the source code location of the call site or allocation. 217 218 The ``(root)`` node's count includes objects allocated in the content page by the web browser, like DOM events. Indeed, this display shows that ``popup.xml`` and ``content.js``, which are internal components of Firefox, allocated more objects in the page's compartment than the page itself. (We will probably revise the allocation log to present such allocations in a way that is more informative, and that exposes less of Firefox's internal structure.) 219 220 As expected, the ``onclick`` handler is responsible for all allocation done by the page's own code. (The line number for the onclick handler is ``1``, indicating that the allocating call is located on line one of the handler text itself. We will probably change this to be the line number within ``page.html``, not the line number within the handler code.) 221 222 The ``onclick`` handler calls ``doDivsAndSpans``, which calls ``divsAndSpans``, which invokes closures of ``factory`` to do all the actual allocation. (It is unclear why ``spanFactory`` allocated thirteen objects, despite being called only ten times.) 223 224 225 226 Source Metadata 227 --------------- 228 229 Generated from file: 230 js/src/doc/Debugger/Tutorial-Alloc-Log-Tree.md 231 232 Watermark: 233 sha256:b56f6df61c39dbe19ca1f49752aea42207c804355513f4fea8249bdeb4cb056d 234 Changeset: 235 `251fccc1f62b <https://hg.mozilla.org/mozilla-central/rev/251fccc1f62b>`_