tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>`_