チュートリアル: コールパスごとの割り当てを表示する

チュートリアル: コールパスごとの割り当てを表示する

このページではデバッガ API を使用して、Web ページが割り当てたオブジェクトの数を、それらを割り当てた関数呼び出しパスでソートして表示する方法を示します。

  1. about:config という URL を参照し、devtools.chrome.enabled プリファレンスを true に設定します。

    Setting the devtools.chrome.enabled preference

    'devtools.chrome.enabled' プリファレンスを設定します

  2. 開発者の Scratchpad (メニューボタン > 開発者 > Scratchpad) を開き、「環境」メニューから「ブラウザ」を選択します。(このメニューは、上で説明したように環境設定を変更しない限り表示されません)。

    Selecting the browser context in the Scratchpad

    Scratchpadで 'browser' コンテキストを選択する

  3. スクラッチパッドに次のコードを入力します。

    // This simply defines the 'Debugger' constructor in this
    // Scratchpad; it doesn't actually start debugging anything.
    Components.utils.import('resource://gre/modules/jsdebugger.jsm');
    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, '');
      }
    })();
  4. スクラッチパッドでテキストが選択されていないことを確認し、「実行」ボタンを押します。(Components.utils が定義されていないというエラーが表示された場合は、手順2で説明したように、スクラッチパッドの Environment メニューから Browser を選択していることを確認してください)

  5. 次の HTML テキストをファイルに保存し、ブラウザでファイルにアクセスします。現在のブラウザタブにこのページが表示されていることを確認してください。

    <div onclick="doDivsAndSpans()">
      Click here to make the page do some allocations.
    </div>
    
    <script>
      function makeFactory(type) {
        return function factory(content) {
          var elt = document.createElement(type);
          elt.textContent = content;
          return elt;
        };
      }
    
      var divFactory = makeFactory('div');
      var spanFactory = makeFactory('span');
    
      function divsAndSpans() {
        for (i = 0; i < 10; i++) {
          var div = divFactory('div #' + i);
          div.appendChild(spanFactory('span #' + i));
          document.body.appendChild(div);
        }
      }
    
      function doDivsAndSpans() { divsAndSpans(); }
    </script>
  6. ブラウザコンソール (メニューボタン > 開発者 > ブラウザコンソール) を開き、ブラウザコンソールで demoPlotAllocations() 式を評価します。これにより、現在のブラウザータブにロギングのロギングが開始されます。

  7. ブラウザのタブで、「ここをクリックしてください...」というテキストをクリックします。イベントハンドラは、ページの最後にテキストを追加する必要があります。

  8. ブラウザのコンソールに戻り、demoPlotAllocations() 式を評価します。これにより、割り当てのロギングが停止し、割り当てのツリーが表示されます。

    An allocation plot, displayed in the console

    コンソールに表示される割り当てプロット

    各行の左端の数字は、そのサイトまたはそこから呼び出されたサイトで割り当てられたオブジェクトの総数を示します。カウントの後、関数名と呼び出しサイトまたは割り当てのソースコードの場所が表示されます。

    (root) ノードのカウントには、DOM イベントのように、Web ブラウザによってコンテンツページに割り当てられたオブジェクトが含まれます。実際、この表示は Firefox の内部コンポーネントである popup.xmlcontent.js が、ページ自体よりも多くのオブジェクトをページ区画に割り当てたことを示しています。(割り振りログを修正してより多くの情報を提供し、Firefox の内部構造をより少なくする方法で、そのような割り当てを提示するでしょう)。

    予想通り、onclick ハンドラは、ページ自身のコードによって行われるすべての割り当てを処理します。(onclick ハンドラの行番号は 1 で、割り付けの呼び出しがハンドラテキストの1行目にあることを示しています。ハンドラコード内の行番号ではなく、page.html 内の行番号に変更します。)

    onclick ハンドラは、divsAndSpans を呼び出す doDivsAndSpans を呼び出します。これは実際の割り当てをすべて行うために factory のクロージャを呼び出します。(なぜ、spanFactory が10回だけ呼び出されたにもかかわらず、13個のオブジェクトを割り当てたのかは不明です。)

ソースのメタデータ

生成元ファイル:
 
js/src/doc/Debugger/Tutorial-Alloc-Log-Tree.md
透かし:
sha256:b56f6df61c39dbe19ca1f49752aea42207c804355513f4fea8249bdeb4cb056d
変更セット:
251fccc1f62b

ドキュメントのタグと貢献者

このページの貢献者: silverskyvicto
最終更新者: silverskyvicto,