Bug 1119933 - Filter out platform frames in the flamegraph for the new performance tool, r=jsantell
authorVictor Porof <vporof@mozilla.com>
Thu, 15 Jan 2015 14:54:46 -0500
changeset 224320 aeffa71cbbb0e95c038bdc5f094c0ce710e89ade
parent 224319 b8b515f7197beb3a9b9afd043ba6d0d3dc5d3712
child 224321 f5053a5e716adcc612bf415cc23121f96381dc4f
push id54190
push userkwierso@gmail.com
push dateSat, 17 Jan 2015 02:06:29 +0000
treeherdermozilla-inbound@369a8f14ccf8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjsantell
bugs1119933
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1119933 - Filter out platform frames in the flamegraph for the new performance tool, r=jsantell
browser/devtools/performance/performance-controller.js
browser/devtools/performance/views/details-flamegraph.js
browser/devtools/profiler/test/browser_profiler_content-check.js
browser/devtools/profiler/utils/tree-model.js
browser/devtools/shared/test/browser.ini
browser/devtools/shared/test/browser_flame-graph-utils-03.js
browser/devtools/shared/widgets/FlameGraph.jsm
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -18,44 +18,48 @@ devtools.lazyRequireGetter(this, "DevToo
   "devtools/toolkit/DevToolsUtils");
 
 devtools.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
   "devtools/timeline/global", true);
 devtools.lazyRequireGetter(this, "L10N",
   "devtools/profiler/global", true);
 devtools.lazyRequireGetter(this, "PerformanceIO",
   "devtools/performance/io", true);
+devtools.lazyRequireGetter(this, "RecordingModel",
+  "devtools/performance/recording-model", true);
+devtools.lazyRequireGetter(this, "RECORDING_IN_PROGRESS",
+  "devtools/performance/recording-model", true);
+devtools.lazyRequireGetter(this, "RECORDING_UNAVAILABLE",
+  "devtools/performance/recording-model", true);
+
 devtools.lazyRequireGetter(this, "MarkersOverview",
   "devtools/timeline/markers-overview", true);
 devtools.lazyRequireGetter(this, "MemoryOverview",
   "devtools/timeline/memory-overview", true);
 devtools.lazyRequireGetter(this, "Waterfall",
   "devtools/timeline/waterfall", true);
 devtools.lazyRequireGetter(this, "MarkerDetails",
   "devtools/timeline/marker-details", true);
 devtools.lazyRequireGetter(this, "CallView",
   "devtools/profiler/tree-view", true);
 devtools.lazyRequireGetter(this, "ThreadNode",
   "devtools/profiler/tree-model", true);
-
+devtools.lazyRequireGetter(this, "FrameNode",
+  "devtools/profiler/tree-model", true);
 
 devtools.lazyImporter(this, "CanvasGraphUtils",
   "resource:///modules/devtools/Graphs.jsm");
 devtools.lazyImporter(this, "LineGraphWidget",
   "resource:///modules/devtools/Graphs.jsm");
-devtools.lazyImporter(this, "SideMenuWidget",
-  "resource:///modules/devtools/SideMenuWidget.jsm");
-
-const { RecordingModel, RECORDING_IN_PROGRESS, RECORDING_UNAVAILABLE } =
-  devtools.require("devtools/performance/recording-model");
-
 devtools.lazyImporter(this, "FlameGraphUtils",
   "resource:///modules/devtools/FlameGraph.jsm");
 devtools.lazyImporter(this, "FlameGraph",
   "resource:///modules/devtools/FlameGraph.jsm");
+devtools.lazyImporter(this, "SideMenuWidget",
+  "resource:///modules/devtools/SideMenuWidget.jsm");
 
 // Events emitted by various objects in the panel.
 const EVENTS = {
   // Emitted by the PerformanceController or RecordingView
   // when a recording model is selected
   RECORDING_SELECTED: "Performance:RecordingSelected",
 
   // Emitted by the PerformanceView on record button click
--- a/browser/devtools/performance/views/details-flamegraph.js
+++ b/browser/devtools/performance/views/details-flamegraph.js
@@ -38,17 +38,18 @@ let FlameGraphView = {
    */
   render: function (profilerData) {
     // Empty recordings might yield no profiler data.
     if (profilerData.profile == null) {
       return;
     }
     let samples = profilerData.profile.threads[0].samples;
     let dataSrc = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
-      flattenRecursion: Prefs.flattenTreeRecursion
+      flattenRecursion: Prefs.flattenTreeRecursion,
+      filterFrames: !Prefs.showPlatformData && FrameNode.isContent,
     });
     this.graph.setData(dataSrc);
     this.emit(EVENTS.FLAMEGRAPH_RENDERED);
   },
 
   /**
    * Called when recording is stopped.
    */
--- a/browser/devtools/profiler/test/browser_profiler_content-check.js
+++ b/browser/devtools/profiler/test/browser_profiler_content-check.js
@@ -2,50 +2,50 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests the function testing whether or not a frame is content or chrome
  * works properly.
  */
 
 function test() {
-  let { _isContent } = devtools.require("devtools/profiler/tree-model");
+  let { FrameNode } = devtools.require("devtools/profiler/tree-model");
 
-  ok(_isContent({ location: "http://foo" }),
+  ok(FrameNode.isContent({ location: "http://foo" }),
     "Verifying content/chrome frames is working properly.");
-  ok(_isContent({ location: "https://foo" }),
+  ok(FrameNode.isContent({ location: "https://foo" }),
     "Verifying content/chrome frames is working properly.");
-  ok(_isContent({ location: "file://foo" }),
+  ok(FrameNode.isContent({ location: "file://foo" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!_isContent({ location: "chrome://foo" }),
+  ok(!FrameNode.isContent({ location: "chrome://foo" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!_isContent({ location: "resource://foo" }),
+  ok(!FrameNode.isContent({ location: "resource://foo" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!_isContent({ location: "chrome://foo -> http://bar" }),
+  ok(!FrameNode.isContent({ location: "chrome://foo -> http://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!_isContent({ location: "chrome://foo -> https://bar" }),
+  ok(!FrameNode.isContent({ location: "chrome://foo -> https://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!_isContent({ location: "chrome://foo -> file://bar" }),
+  ok(!FrameNode.isContent({ location: "chrome://foo -> file://bar" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!_isContent({ location: "resource://foo -> http://bar" }),
+  ok(!FrameNode.isContent({ location: "resource://foo -> http://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!_isContent({ location: "resource://foo -> https://bar" }),
+  ok(!FrameNode.isContent({ location: "resource://foo -> https://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!_isContent({ location: "resource://foo -> file://bar" }),
+  ok(!FrameNode.isContent({ location: "resource://foo -> file://bar" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!_isContent({ category: 1, location: "chrome://foo" }),
+  ok(!FrameNode.isContent({ category: 1, location: "chrome://foo" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!_isContent({ category: 1, location: "resource://foo" }),
+  ok(!FrameNode.isContent({ category: 1, location: "resource://foo" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!_isContent({ category: 1, location: "file://foo -> http://bar" }),
+  ok(!FrameNode.isContent({ category: 1, location: "file://foo -> http://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!_isContent({ category: 1, location: "file://foo -> https://bar" }),
+  ok(!FrameNode.isContent({ category: 1, location: "file://foo -> https://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!_isContent({ category: 1, location: "file://foo -> file://bar" }),
+  ok(!FrameNode.isContent({ category: 1, location: "file://foo -> file://bar" }),
     "Verifying content/chrome frames is working properly.");
 
   finish();
 }
--- a/browser/devtools/profiler/utils/tree-model.js
+++ b/browser/devtools/profiler/utils/tree-model.js
@@ -13,17 +13,17 @@ loader.lazyRequireGetter(this, "CATEGORY
 loader.lazyRequireGetter(this, "CATEGORY_JIT",
   "devtools/profiler/global", true);
 
 const CHROME_SCHEMES = ["chrome://", "resource://"];
 const CONTENT_SCHEMES = ["http://", "https://", "file://"];
 
 exports.ThreadNode = ThreadNode;
 exports.FrameNode = FrameNode;
-exports._isContent = isContent; // used in tests
+exports.FrameNode.isContent = isContent;
 
 /**
  * A call tree for a thread. This is essentially a linkage between all frames
  * of all samples into a single tree structure, with additional information
  * on each node, like the time spent (in milliseconds) and samples count.
  *
  * Example:
  * {
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -17,16 +17,17 @@ support-files =
 [browser_cubic-bezier-03.js]
 [browser_flame-graph-01.js]
 [browser_flame-graph-02.js]
 [browser_flame-graph-03a.js]
 [browser_flame-graph-03b.js]
 [browser_flame-graph-04.js]
 [browser_flame-graph-utils-01.js]
 [browser_flame-graph-utils-02.js]
+[browser_flame-graph-utils-03.js]
 [browser_graphs-01.js]
 [browser_graphs-02.js]
 [browser_graphs-03.js]
 [browser_graphs-04.js]
 [browser_graphs-05.js]
 [browser_graphs-06.js]
 [browser_graphs-07a.js]
 [browser_graphs-07b.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_flame-graph-utils-03.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests if platform frames are removed from the flame graph data.
+
+let {FlameGraphUtils} = Cu.import("resource:///modules/devtools/FlameGraph.jsm", {});
+let {FrameNode} = devtools.require("devtools/profiler/tree-model");
+
+let test = Task.async(function*() {
+  yield promiseTab("about:blank");
+  yield performTest();
+  gBrowser.removeCurrentTab();
+  finish();
+});
+
+function* performTest() {
+  let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
+    filterFrames: FrameNode.isContent
+  });
+
+  ok(out, "Some data was outputted properly");
+  is(out.length, 10, "The outputted length is correct.");
+
+  info("Got flame graph data:\n" + out.toSource() + "\n");
+
+  for (let i = 0; i < out.length; i++) {
+    let found = out[i];
+    let expected = EXPECTED_OUTPUT[i];
+
+    is(found.blocks.length, expected.blocks.length,
+      "The correct number of blocks were found in this bucket.");
+
+    for (let j = 0; j < found.blocks.length; j++) {
+      is(found.blocks[j].x, expected.blocks[j].x,
+        "The expected block X position is correct for this frame.");
+      is(found.blocks[j].y, expected.blocks[j].y,
+        "The expected block Y position is correct for this frame.");
+      is(found.blocks[j].width, expected.blocks[j].width,
+        "The expected block width is correct for this frame.");
+      is(found.blocks[j].height, expected.blocks[j].height,
+        "The expected block height is correct for this frame.");
+      is(found.blocks[j].text, expected.blocks[j].text,
+        "The expected block text is correct for this frame.");
+    }
+  }
+}
+
+let TEST_DATA = [{
+  frames: [{
+    location: "http://A"
+  }, {
+    location: "https://B"
+  }, {
+    location: "file://C",
+  }, {
+    location: "chrome://D"
+  }, {
+    location: "resource://E"
+  }],
+  time: 50,
+}];
+
+let EXPECTED_OUTPUT = [{
+  blocks: []
+}, {
+  blocks: []
+}, {
+  blocks: [{
+    srcData: {
+      startTime: 0,
+      rawLocation: "http://A"
+    },
+    x: 0,
+    y: 0,
+    width: 50,
+    height: 11,
+    text: "http://A"
+  }, {
+    srcData: {
+      startTime: 0,
+      rawLocation: "file://C"
+    },
+    x: 0,
+    y: 22,
+    width: 50,
+    height: 11,
+    text: "file://C"
+  }]
+}, {
+  blocks: []
+}, {
+  blocks: []
+}, {
+  blocks: []
+}, {
+  blocks: []
+}, {
+  blocks: []
+}, {
+  blocks: [{
+    srcData: {
+      startTime: 0,
+      rawLocation: "https://B"
+    },
+    x: 0,
+    y: 11,
+    width: 50,
+    height: 11,
+    text: "https://B"
+  }]
+}, {
+  blocks: []
+}];
--- a/browser/devtools/shared/widgets/FlameGraph.jsm
+++ b/browser/devtools/shared/widgets/FlameGraph.jsm
@@ -824,17 +824,20 @@ let FlameGraphUtils = {
   /**
    * Converts a list of samples from the profiler data to something that's
    * drawable by a FlameGraph widget.
    *
    * @param array samples
    *        A list of { time, frames: [{ location }] } objects.
    * @param object options [optional]
    *        Additional options supported by this operation:
-   *          - flattenRecursion: specifies if consecutive frames are omitted
+   *          - flattenRecursion: specifies if identical consecutive frames
+   *                              should be omitted from the output
+   *          - filterFrames: predicate used for filtering all frames, passing
+   *                          in each frame, its index and the sample array
    * @param array out [optional]
    *        An output storage to reuse for storing the flame graph data.
    * @return array
    *         The flame graph data.
    */
   createFlameGraphDataFromSamples: function(samples, options = {}, out = []) {
     // 1. Create a map of colors to arrays, representing buckets of
     // blocks inside the flame graph pyramid sharing the same style.
@@ -854,16 +857,23 @@ let FlameGraphUtils = {
       let frameIndex = 0;
 
       // Flatten recursion if preferred, by removing consecutive frames
       // sharing the same location.
       if (options.flattenRecursion) {
         frames = frames.filter(this._isConsecutiveDuplicate);
       }
 
+      // Apply a provided filter function. This can be used, for example, to
+      // filter out platform frames if only content-related function calls
+      // should be taken into consideration.
+      if (options.filterFrames) {
+        frames = frames.filter(options.filterFrames);
+      }
+
       for (let { location } of frames) {
         let prevFrame = prevFrames[frameIndex];
 
         // Frames at the same location and the same depth will be reused.
         // If there is a block already created, change its width.
         if (prevFrame && prevFrame.srcData.rawLocation == location) {
           prevFrame.width = (time - prevFrame.srcData.startTime);
         }