Bug 1462784 - Update the devtools performance panel for the new category list. r=gregtatum
☠☠ backed out by e8eba439b33e ☠ ☠
authorMarkus Stange <mstange@themasta.com>
Wed, 23 May 2018 23:11:41 -0400
changeset 420805 a74d365984427c6a7c1e8c4c37ee13aea14142bf
parent 420804 c8192175f3601a780affbacdb41fa1a8ef71fe50
child 420806 11670c51f572620b053cd342f8a7d0d3f5825720
push id103894
push usercsabou@mozilla.com
push dateFri, 01 Jun 2018 09:46:36 +0000
treeherdermozilla-inbound@e99ff79303ea [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgregtatum
bugs1462784
milestone62.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 1462784 - Update the devtools performance panel for the new category list. r=gregtatum MozReview-Commit-ID: HwRFEfgA4L
devtools/client/locales/en-US/performance.properties
devtools/client/performance/modules/categories.js
devtools/client/performance/modules/logic/frame-utils.js
devtools/client/performance/test/browser_perf-tree-view-08.js
devtools/client/performance/test/helpers/synth-utils.js
devtools/client/performance/test/unit/test_profiler-categories.js
devtools/client/performance/test/unit/test_tree-model-07.js
devtools/client/performance/test/unit/test_tree-model-08.js
devtools/client/performance/test/unit/test_tree-model-09.js
devtools/client/shared/widgets/FlameGraph.js
--- a/devtools/client/locales/en-US/performance.properties
+++ b/devtools/client/locales/en-US/performance.properties
@@ -58,23 +58,23 @@ graphs.ms=ms
 # AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
 graphs.memory=MB
 
 # LOCALIZATION NOTE (category.*):
 # These strings are displayed in the categories graph of the Performance Tools,
 # as the legend for each block in every bar. These labels should be kept
 # AS SHORT AS POSSIBLE so they don't obstruct important parts of the graph.
 category.other=Gecko
-category.css=Styles
+category.layout=Layout
 category.js=JIT
 category.gc=GC
 category.network=Network
 category.graphics=Graphics
-category.storage=Storage
-category.events=Input & Events
+category.dom=DOM
+category.idle=Idle
 category.tools=Tools
 
 # LOCALIZATION NOTE (table.bytes):
 # This string is displayed in the call tree after bytesize units.
 # %S represents the value in bytes.
 table.bytes=%S B
 
 # LOCALIZATION NOTE (table.ms2):
--- a/devtools/client/performance/modules/categories.js
+++ b/devtools/client/performance/modules/categories.js
@@ -2,26 +2,30 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { L10N } = require("devtools/client/performance/modules/global");
 
 /**
  * Details about each label stack frame category.
- * @see CATEGORY_MAPPINGS.
+ * To be kept in sync with the js::ProfilingStackFrame::Category in ProfilingStack.h
  */
 const CATEGORIES = [{
+  color: "#d99b28",
+  abbrev: "idle",
+  label: L10N.getStr("category.idle")
+}, {
   color: "#5e88b0",
   abbrev: "other",
   label: L10N.getStr("category.other")
 }, {
   color: "#46afe3",
-  abbrev: "css",
-  label: L10N.getStr("category.css")
+  abbrev: "layout",
+  label: L10N.getStr("category.layout")
 }, {
   color: "#d96629",
   abbrev: "js",
   label: L10N.getStr("category.js")
 }, {
   color: "#eb5368",
   abbrev: "gc",
   label: L10N.getStr("category.gc")
@@ -30,99 +34,40 @@ const CATEGORIES = [{
   abbrev: "network",
   label: L10N.getStr("category.network")
 }, {
   color: "#70bf53",
   abbrev: "graphics",
   label: L10N.getStr("category.graphics")
 }, {
   color: "#8fa1b2",
-  abbrev: "storage",
-  label: L10N.getStr("category.storage")
+  abbrev: "dom",
+  label: L10N.getStr("category.dom")
 }, {
-  color: "#d99b28",
-  abbrev: "events",
-  label: L10N.getStr("category.events")
-}, {
+  // The devtools-only "tools" category which gets computed by
+  // computeIsContentAndCategory and is not part of the category list in
+  // ProfilingStack.h.
   color: "#8fa1b2",
   abbrev: "tools",
   label: L10N.getStr("category.tools")
 }];
 
 /**
- * Mapping from category bitmasks in the profiler data to additional details.
- * To be kept in sync with the js::ProfilingStackFrame::Category in ProfilingStack.h
+ * Get the numeric index for the given category abbreviation.
+ * See `CATEGORIES` above.
  */
-const CATEGORY_MAPPINGS = {
-  // js::ProfilingStackFrame::Category::OTHER
-  "16": CATEGORIES[0],
-  // js::ProfilingStackFrame::Category::CSS
-  "32": CATEGORIES[1],
-  // js::ProfilingStackFrame::Category::JS
-  "64": CATEGORIES[2],
-  // js::ProfilingStackFrame::Category::GC
-  "128": CATEGORIES[3],
-  // js::ProfilingStackFrame::Category::CC
-  "256": CATEGORIES[3],
-  // js::ProfilingStackFrame::Category::NETWORK
-  "512": CATEGORIES[4],
-  // js::ProfilingStackFrame::Category::GRAPHICS
-  "1024": CATEGORIES[5],
-  // js::ProfilingStackFrame::Category::STORAGE
-  "2048": CATEGORIES[6],
-  // js::ProfilingStackFrame::Category::EVENTS
-  "4096": CATEGORIES[7],
-  // non-bitmasks for specially-assigned categories
-  "9000": CATEGORIES[8],
-};
-
-/**
- * Get the numeric bitmask (or set of masks) for the given category
- * abbreviation. See `CATEGORIES` and `CATEGORY_MAPPINGS` above.
- *
- * CATEGORY_MASK can be called with just a name if it is expected that the
- * category is mapped to by exactly one bitmask. If the category is mapped
- * to by multiple masks, CATEGORY_MASK for that name must be called with
- * an additional argument specifying the desired id (in ascending order).
- */
-const [CATEGORY_MASK, CATEGORY_MASK_LIST] = (() => {
-  let bitmasksForCategory = {};
-  let all = Object.keys(CATEGORY_MAPPINGS);
-
-  for (let category of CATEGORIES) {
-    bitmasksForCategory[category.abbrev] = all
-      .filter(mask => CATEGORY_MAPPINGS[mask] == category)
-      .map(mask => +mask)
-      .sort();
+const CATEGORY_INDEX = (() => {
+  let indexForCategory = {};
+  for (let categoryIndex = 0; categoryIndex < CATEGORIES.length; categoryIndex++) {
+    const category = CATEGORIES[categoryIndex];
+    indexForCategory[category.abbrev] = categoryIndex;
   }
 
-  return [
-    function(name, index) {
-      if (!(name in bitmasksForCategory)) {
-        throw new Error(`Category abbreviation "${name}" does not exist.`);
-      }
-      if (arguments.length == 1) {
-        if (bitmasksForCategory[name].length != 1) {
-          throw new Error(`Expected exactly one category number for "${name}".`);
-        } else {
-          return bitmasksForCategory[name][0];
-        }
-      } else {
-        if (index > bitmasksForCategory[name].length) {
-          throw new Error(`Index "${index}" too high for category "${name}".`);
-        }
-        return bitmasksForCategory[name][index - 1];
-      }
-    },
-
-    function(name) {
-      if (!(name in bitmasksForCategory)) {
-        throw new Error(`Category abbreviation "${name}" does not exist.`);
-      }
-      return bitmasksForCategory[name];
+  return function(name) {
+    if (!(name in indexForCategory)) {
+      throw new Error(`Category abbreviation "${name}" does not exist.`);
     }
-  ];
+    return indexForCategory[name];
+  };
 })();
 
 exports.CATEGORIES = CATEGORIES;
-exports.CATEGORY_MAPPINGS = CATEGORY_MAPPINGS;
-exports.CATEGORY_MASK = CATEGORY_MASK;
-exports.CATEGORY_MASK_LIST = CATEGORY_MASK_LIST;
+exports.CATEGORY_INDEX = CATEGORY_INDEX;
--- a/devtools/client/performance/modules/logic/frame-utils.js
+++ b/devtools/client/performance/modules/logic/frame-utils.js
@@ -4,17 +4,17 @@
 "use strict";
 
 const global = require("devtools/client/performance/modules/global");
 const demangle = require("devtools/client/shared/demangle");
 const { assert } = require("devtools/shared/DevToolsUtils");
 const { isChromeScheme, isContentScheme, isWASM, parseURL } =
   require("devtools/client/shared/source-utils");
 
-const { CATEGORY_MASK, CATEGORY_MAPPINGS } = require("devtools/client/performance/modules/categories");
+const { CATEGORY_INDEX, CATEGORIES } = require("devtools/client/performance/modules/categories");
 
 // Character codes used in various parsing helper functions.
 const CHAR_CODE_R = "r".charCodeAt(0);
 const CHAR_CODE_0 = "0".charCodeAt(0);
 const CHAR_CODE_9 = "9".charCodeAt(0);
 const CHAR_CODE_CAP_Z = "Z".charCodeAt(0);
 
 const CHAR_CODE_LPAREN = "(".charCodeAt(0);
@@ -185,17 +185,17 @@ function parseLocation(location, fallbac
 
 /**
  * Sets the properties of `isContent` and `category` on a frame.
  *
  * @param {InflatedFrame} frame
  */
 function computeIsContentAndCategory(frame) {
   // Only C++ stack frames have associated category information.
-  if (frame.category) {
+  if (frame.category !== null && frame.category !== undefined) {
     return;
   }
 
   let location = frame.location;
 
   // There are 3 variants of location strings in the profiler (with optional
   // column numbers):
   //   1) "name (resource:line)"
@@ -229,28 +229,28 @@ function computeIsContentAndCategory(fra
   }
 
   if (schemeStartIndex !== 0) {
     for (let j = schemeStartIndex; j < location.length; j++) {
       if (location.charCodeAt(j) === CHAR_CODE_R &&
           isChromeScheme(location, j) &&
           (location.includes("resource://devtools") ||
            location.includes("resource://devtools"))) {
-        frame.category = CATEGORY_MASK("tools");
+        frame.category = CATEGORY_INDEX("tools");
         return;
       }
     }
   }
 
   if (location === "EnterJIT") {
-    frame.category = CATEGORY_MASK("js");
+    frame.category = CATEGORY_INDEX("js");
     return;
   }
 
-  frame.category = CATEGORY_MASK("other");
+  frame.category = CATEGORY_INDEX("other");
 }
 
 /**
  * Get caches to cache inflated frames and computed frame keys of a frame
  * table.
  *
  * @param object framesTable
  * @return object
@@ -388,17 +388,17 @@ function getFrameInfo(node, options) {
       data.functionName = global.L10N.getStr("table.root");
     } else {
       data = parseLocation(node.location, node.line, node.column);
       data.hasOptimizations = node.hasOptimizations();
       data.isContent = node.isContent;
       data.isMetaCategory = node.isMetaCategory;
     }
     data.samples = node.youngestFrameSamples;
-    data.categoryData = CATEGORY_MAPPINGS[node.category] || {};
+    data.categoryData = CATEGORIES[node.category] || CATEGORIES[CATEGORY_INDEX("other")];
     data.nodeType = node.nodeType;
 
     // Frame name (function location or some meta information)
     if (data.isMetaCategory) {
       data.name = data.categoryData.label;
     } else if (shouldDemangle(data.functionName)) {
       data.name = demangle(data.functionName);
     } else {
--- a/devtools/client/performance/test/browser_perf-tree-view-08.js
+++ b/devtools/client/performance/test/browser_perf-tree-view-08.js
@@ -4,17 +4,17 @@
 
 /**
  * Tests that the profiler's tree view renders generalized platform data
  * when `contentOnly` is on correctly.
  */
 
 const { ThreadNode } = require("devtools/client/performance/modules/logic/tree-model");
 const { CallView } = require("devtools/client/performance/modules/widgets/tree-view");
-const { CATEGORY_MASK } = require("devtools/client/performance/modules/categories");
+const { CATEGORY_INDEX } = require("devtools/client/performance/modules/categories");
 const RecordingUtils = require("devtools/shared/performance/recording-utils");
 
 add_task(function() {
   let threadNode = new ThreadNode(gProfile.threads[0], { startTime: 0, endTime: 20,
                                                          contentOnly: true });
 
   // Don't display the synthesized (root) and the real (root) node twice.
   threadNode.calls = threadNode.calls[0].calls;
@@ -84,26 +84,26 @@ const gProfile = RecordingUtils.deflateP
       ]
     }, {
       time: 1 + 1 + 2,
       frames: [
         { location: "(root)" },
         { location: "http://content/A" },
         { location: "http://content/E" },
         { location: "http://content/F" },
-        { location: "platform_JS", category: CATEGORY_MASK("js") },
+        { location: "platform_JS", category: CATEGORY_INDEX("js") },
       ]
     }, {
       time: 1 + 1 + 2 + 3,
       frames: [
         { location: "(root)" },
-        { location: "platform_JS2", category: CATEGORY_MASK("js") },
+        { location: "platform_JS2", category: CATEGORY_INDEX("js") },
       ]
     }, {
       time: 1 + 1 + 2 + 3 + 5,
       frames: [
         { location: "(root)" },
         { location: "http://content/A" },
-        { location: "platform_GC", category: CATEGORY_MASK("gc", 1) },
+        { location: "platform_GC", category: CATEGORY_INDEX("gc") },
       ]
     }]
   }]
 });
--- a/devtools/client/performance/test/helpers/synth-utils.js
+++ b/devtools/client/performance/test/helpers/synth-utils.js
@@ -1,53 +1,53 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 /**
  * Generates a generalized profile with some samples.
  */
 exports.synthesizeProfile = () => {
-  const { CATEGORY_MASK } = require("devtools/client/performance/modules/categories");
+  const { CATEGORY_INDEX } = require("devtools/client/performance/modules/categories");
   const RecordingUtils = require("devtools/shared/performance/recording-utils");
 
   return RecordingUtils.deflateProfile({
     meta: { version: 2 },
     threads: [{
       samples: [{
         time: 1,
         frames: [
-          { category: CATEGORY_MASK("other"), location: "(root)" },
-          { category: CATEGORY_MASK("other"), location: "A (http://foo/bar/baz:12)" },
-          { category: CATEGORY_MASK("css"), location: "B (http://foo/bar/baz:34)" },
-          { category: CATEGORY_MASK("js"), location: "C (http://foo/bar/baz:56)" }
+          { category: CATEGORY_INDEX("other"), location: "(root)" },
+          { category: CATEGORY_INDEX("other"), location: "A (http://foo/bar/baz:12)" },
+          { category: CATEGORY_INDEX("css"), location: "B (http://foo/bar/baz:34)" },
+          { category: CATEGORY_INDEX("js"), location: "C (http://foo/bar/baz:56)" }
         ]
       }, {
         time: 1 + 1,
         frames: [
-          { category: CATEGORY_MASK("other"), location: "(root)" },
-          { category: CATEGORY_MASK("other"), location: "A (http://foo/bar/baz:12)" },
-          { category: CATEGORY_MASK("css"), location: "B (http://foo/bar/baz:34)" },
-          { category: CATEGORY_MASK("gc", 1), location: "D (http://foo/bar/baz:78:9)" }
+          { category: CATEGORY_INDEX("other"), location: "(root)" },
+          { category: CATEGORY_INDEX("other"), location: "A (http://foo/bar/baz:12)" },
+          { category: CATEGORY_INDEX("css"), location: "B (http://foo/bar/baz:34)" },
+          { category: CATEGORY_INDEX("gc"), location: "D (http://foo/bar/baz:78:9)" }
         ]
       }, {
         time: 1 + 1 + 2,
         frames: [
-          { category: CATEGORY_MASK("other"), location: "(root)" },
-          { category: CATEGORY_MASK("other"), location: "A (http://foo/bar/baz:12)" },
-          { category: CATEGORY_MASK("css"), location: "B (http://foo/bar/baz:34)" },
-          { category: CATEGORY_MASK("gc", 1), location: "D (http://foo/bar/baz:78:9)" }
+          { category: CATEGORY_INDEX("other"), location: "(root)" },
+          { category: CATEGORY_INDEX("other"), location: "A (http://foo/bar/baz:12)" },
+          { category: CATEGORY_INDEX("css"), location: "B (http://foo/bar/baz:34)" },
+          { category: CATEGORY_INDEX("gc"), location: "D (http://foo/bar/baz:78:9)" }
         ]
       }, {
         time: 1 + 1 + 2 + 3,
         frames: [
-          { category: CATEGORY_MASK("other"), location: "(root)" },
-          { category: CATEGORY_MASK("other"), location: "A (http://foo/bar/baz:12)" },
-          { category: CATEGORY_MASK("gc", 2), location: "E (http://foo/bar/baz:90)" },
-          { category: CATEGORY_MASK("network"), location: "F (http://foo/bar/baz:99)" }
+          { category: CATEGORY_INDEX("other"), location: "(root)" },
+          { category: CATEGORY_INDEX("other"), location: "A (http://foo/bar/baz:12)" },
+          { category: CATEGORY_INDEX("gc"), location: "E (http://foo/bar/baz:90)" },
+          { category: CATEGORY_INDEX("network"), location: "F (http://foo/bar/baz:99)" }
         ]
       }]
     }]
   });
 };
 
 /**
  * Generates a simple implementation for a tree class.
--- a/devtools/client/performance/test/unit/test_profiler-categories.js
+++ b/devtools/client/performance/test/unit/test_profiler-categories.js
@@ -2,32 +2,24 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 /**
  * Tests if the profiler categories are mapped correctly.
  */
 
 add_task(function() {
-  let { CATEGORIES, CATEGORY_MAPPINGS } = require("devtools/client/performance/modules/categories");
+  let { CATEGORIES } = require("devtools/client/performance/modules/categories");
   let { L10N } = require("devtools/client/performance/modules/global");
   let count = CATEGORIES.length;
 
   ok(count,
     "Should have a non-empty list of categories available.");
 
   ok(CATEGORIES.some(e => e.color),
     "All categories have an associated color.");
 
   ok(CATEGORIES.every(e => e.label),
     "All categories have an associated label.");
 
   ok(CATEGORIES.every(e => e.label === L10N.getStr("category." + e.abbrev)),
     "All categories have a correctly localized label.");
-
-  ok(Object.keys(CATEGORY_MAPPINGS).every(e => (Number(e) >= 9000 && Number(e) <= 9999) ||
-                                                Number.isInteger(Math.log2(e))),
-    "All bitmask mappings keys are powers of 2, or between 9000-9999 for special " +
-    "categories.");
-
-  ok(Object.keys(CATEGORY_MAPPINGS).every(e => CATEGORIES.includes(CATEGORY_MAPPINGS[e])),
-    "All bitmask mappings point to a category.");
 });
--- a/devtools/client/performance/test/unit/test_tree-model-07.js
+++ b/devtools/client/performance/test/unit/test_tree-model-07.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 /**
  * Tests that when displaying only content nodes, platform nodes are generalized.
  */
 
-var { CATEGORY_MASK } = require("devtools/client/performance/modules/categories");
+var { CATEGORY_INDEX } = require("devtools/client/performance/modules/categories");
 
 add_task(function test() {
   let { ThreadNode } = require("devtools/client/performance/modules/logic/tree-model");
   let url = (n) => `http://content/${n}`;
 
   // Create a root node from a given samples array.
 
   let root = getFrameNodePath(new ThreadNode(gThread, { startTime: 5, endTime: 30,
@@ -30,32 +30,39 @@ add_task(function test() {
    *       - F
    *         - (JS)
    */
 
   // Test the root node.
 
   equal(root.calls.length, 2, "root has 2 children");
   ok(getFrameNodePath(root, url("A")), "root has content child");
-  ok(getFrameNodePath(root, "64"), "root has platform generalized child");
-  equal(getFrameNodePath(root, "64").calls.length, 0,
+  ok(getFrameNodePath(root, `${CATEGORY_INDEX("js")}`),
+     "root has platform generalized child");
+  equal(getFrameNodePath(root, `${CATEGORY_INDEX("js")}`).calls.length, 0,
         "platform generalized child is a leaf.");
 
-  ok(getFrameNodePath(root, `${url("A")} > 128`),
+  ok(getFrameNodePath(root, `${url("A")} > ${CATEGORY_INDEX("gc")}`),
      "A has platform generalized child of another type");
-  equal(getFrameNodePath(root, `${url("A")} > 128`).calls.length, 0,
+  equal(getFrameNodePath(root, `${url("A")} > ${CATEGORY_INDEX("gc")}`).calls.length, 0,
         "second generalized type is a leaf.");
 
-  ok(getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 64`),
+  ok(getFrameNodePath(
+       root,
+       `${url("A")} > ${url("E")} > ${url("F")} > ${CATEGORY_INDEX("js")}`
+     ),
      "a second leaf of the first generalized type exists deep in the tree.");
-  ok(getFrameNodePath(root, `${url("A")} > 128`),
+  ok(getFrameNodePath(root, `${url("A")} > ${CATEGORY_INDEX("gc")}`),
      "A has platform generalized child of another type");
 
-  equal(getFrameNodePath(root, "64").category,
-     getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 64`).category,
+  equal(getFrameNodePath(root, `${CATEGORY_INDEX("js")}`).category,
+     getFrameNodePath(
+       root,
+       `${url("A")} > ${url("E")} > ${url("F")} > ${CATEGORY_INDEX("js")}`
+     ).category,
      "generalized frames of same type are duplicated in top-down view");
 });
 
 var gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { location: "(root)" },
     { location: "http://content/A" },
@@ -63,35 +70,35 @@ var gThread = synthesizeProfileForTest([
     { location: "http://content/C" }
   ]
 }, {
   time: 5 + 6,
   frames: [
     { location: "(root)" },
     { location: "http://content/A" },
     { location: "http://content/B" },
-    { location: "contentY", category: CATEGORY_MASK("css") },
+    { location: "contentY", category: CATEGORY_INDEX("layout") },
     { location: "http://content/D" }
   ]
 }, {
   time: 5 + 6 + 7,
   frames: [
     { location: "(root)" },
     { location: "http://content/A" },
-    { location: "contentY", category: CATEGORY_MASK("css") },
+    { location: "contentY", category: CATEGORY_INDEX("layout") },
     { location: "http://content/E" },
     { location: "http://content/F" },
-    { location: "contentY", category: CATEGORY_MASK("js") },
+    { location: "contentY", category: CATEGORY_INDEX("js") },
   ]
 }, {
   time: 5 + 20,
   frames: [
     { location: "(root)" },
-    { location: "contentX", category: CATEGORY_MASK("js") },
+    { location: "contentX", category: CATEGORY_INDEX("js") },
   ]
 }, {
   time: 5 + 25,
   frames: [
     { location: "(root)" },
     { location: "http://content/A" },
-    { location: "contentZ", category: CATEGORY_MASK("gc", 1) },
+    { location: "contentZ", category: CATEGORY_INDEX("gc") },
   ]
 }]);
--- a/devtools/client/performance/test/unit/test_tree-model-08.js
+++ b/devtools/client/performance/test/unit/test_tree-model-08.js
@@ -4,17 +4,17 @@
 
 /**
  * Verifies if FrameNodes retain and parse their data appropriately.
  */
 
 add_task(function test() {
   let FrameUtils = require("devtools/client/performance/modules/logic/frame-utils");
   let { FrameNode } = require("devtools/client/performance/modules/logic/tree-model");
-  let { CATEGORY_MASK } = require("devtools/client/performance/modules/categories");
+  let { CATEGORY_INDEX } = require("devtools/client/performance/modules/categories");
   let compute = frame => {
     FrameUtils.computeIsContentAndCategory(frame);
     return frame;
   };
 
   let frames = [
     new FrameNode("hello/<.world (http://foo/bar.js:123:987)", compute({
       location: "hello/<.world (http://foo/bar.js:123:987)",
@@ -34,17 +34,17 @@ add_task(function test() {
     }), false),
     new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", compute({
       location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)",
       line: 456,
     }), false),
     new FrameNode("Foo::Bar::Baz", compute({
       location: "Foo::Bar::Baz",
       line: 456,
-      category: CATEGORY_MASK("other"),
+      category: CATEGORY_INDEX("other"),
     }), false),
     new FrameNode("EnterJIT", compute({
       location: "EnterJIT",
     }), false),
     new FrameNode("chrome://browser/content/content.js", compute({
       location: "chrome://browser/content/content.js",
       line: 456,
       column: 123
--- a/devtools/client/performance/test/unit/test_tree-model-09.js
+++ b/devtools/client/performance/test/unit/test_tree-model-09.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 /**
  * Tests that when displaying only content nodes, platform nodes are generalized.
  */
 
+var { CATEGORY_INDEX } = require("devtools/client/performance/modules/categories");
+
 add_task(function test() {
   let { ThreadNode } = require("devtools/client/performance/modules/logic/tree-model");
   let url = (n) => `http://content/${n}`;
 
   // Create a root node from a given samples array.
 
   let root = getFrameNodePath(new ThreadNode(gThread, { startTime: 5, endTime: 25,
                                                         contentOnly: true }), "(root)");
@@ -27,26 +29,29 @@ add_task(function test() {
    *       - F
    *         - (Tools)
    */
 
   // Test the root node.
 
   equal(root.calls.length, 2, "root has 2 children");
   ok(getFrameNodePath(root, url("A")), "root has content child");
-  ok(getFrameNodePath(root, "9000"),
+  ok(getFrameNodePath(root, `${CATEGORY_INDEX("tools")}`),
     "root has platform generalized child from Chrome JS");
-  equal(getFrameNodePath(root, "9000").calls.length, 0,
+  equal(getFrameNodePath(root, `${CATEGORY_INDEX("tools")}`).calls.length, 0,
     "platform generalized child is a leaf.");
 
-  ok(getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 9000`),
+  ok(getFrameNodePath(root,
+       `${url("A")} > ${url("E")} > ${url("F")} > ${CATEGORY_INDEX("tools")}`),
      "a second leaf of the generalized Chrome JS exists.");
 
-  equal(getFrameNodePath(root, "9000").category,
-     getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 9000`).category,
+  equal(getFrameNodePath(root, `${CATEGORY_INDEX("tools")}`).category,
+     getFrameNodePath(root,
+       `${url("A")} > ${url("E")} > ${url("F")} > ${CATEGORY_INDEX("tools")}`
+     ).category,
      "generalized frames of same type are duplicated in top-down view");
 });
 
 var gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
     { location: "(root)" },
     { location: "http://content/A" },
--- a/devtools/client/shared/widgets/FlameGraph.js
+++ b/devtools/client/shared/widgets/FlameGraph.js
@@ -8,17 +8,19 @@ const { ELLIPSIS } = require("devtools/s
 
 loader.lazyRequireGetter(this, "defer", "devtools/shared/defer");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/shared/event-emitter");
 
 loader.lazyRequireGetter(this, "getColor",
   "devtools/client/shared/theme", true);
 
-loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
+loader.lazyRequireGetter(this, "CATEGORIES",
+  "devtools/client/performance/modules/categories", true);
+loader.lazyRequireGetter(this, "CATEGORY_INDEX",
   "devtools/client/performance/modules/categories", true);
 loader.lazyRequireGetter(this, "FrameUtils",
   "devtools/client/performance/modules/logic/frame-utils");
 loader.lazyRequireGetter(this, "demangle",
   "devtools/client/shared/demangle");
 
 loader.lazyRequireGetter(this, "AbstractCanvasGraph",
   "devtools/client/shared/widgets/Graphs", true);
@@ -1287,17 +1289,18 @@ var FlameGraphUtils = {
         mutableFrameKeyOptions.isLeaf = stackDepth === 0;
         let frameKey = inflatedFrame.getFrameKey(mutableFrameKeyOptions);
 
         // If not skipping the frame, add it to the current level. The (root)
         // node isn't useful for flame graphs.
         if (frameKey !== "" && frameKey !== "(root)") {
           // If the frame is a meta category, use the category label.
           if (mutableFrameKeyOptions.isMetaCategoryOut) {
-            frameKey = CATEGORY_MAPPINGS[frameKey].label;
+            let category = CATEGORIES[frameKey] || CATEGORIES[CATEGORY_INDEX("other")];
+            frameKey = category.label;
           }
 
           sampleFrames[stackDepth] = inflatedFrame;
           sampleFrameKeys[stackDepth] = frameKey;
 
           // If we shouldn't flatten the current frame into the previous one,
           // increment the stack depth.
           if (!flattenRecursion || frameKey !== prevFrameKey) {