Files
roytam1 98894236c9 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1171200 - Add means of checking if a document links to a manifest. r=billm (066ddad20)
- Bug 1167300 - Consolidate the performance tool directory, r=jsantell (c7dd7dc34)
- Bug 1167300 - Create a way to get strings from multiple localization files, r=jsantell (0973b8d3e)
- modules not in gre (914e4080e)
- Bug 1153011 - Remove zoom button from call tree. r=vporof (797b8f91d)
- Bug 1151973 - Inverted call tree should be ordered by 'self cost', not 'total cost', r=jsantell (f2800b272)
- more gre removal (27aed87a0)
- Bug 1144034 - Flamegraph text is barely readable on non-retina display, r=jsantell (cb19fd9f2)
- Bug 1151973 - Inverted call tree should be ordered by 'self cost', not 'total cost', r=jsantell (9c579599e)
- Bug 1167300 - Fix all performance tool imports to work with the new file locations, r=jsantell (70b2995c4)
- Bug 1167298 - Remove the ordinal property on categories, r=jsantell (00b3f5830)
- Bug 1167733 - Consolidate prefs access and usage in the new performance tool, r=jsantell (4dab15e7f)
- Bug 1167006 - part 3 fully revert merge from 780e1f999f54. (8aaa33c9c)
- Bug 1167961 - Task is incorrectly used in compatibility.js, r=jsantell (7291f68d1)
- Bug 1138641 - Updated remaining callsites to use newChannel2 in browser/devtools (r=vporof) (60ac4b2c8)
- Bug 1164130 - Correctly include RecordingUtils when importing older version 2 profiler data. r=vp (8169d0398)
- Bug 1167962 - Keep exports at bottom of modules, r=jsantell (7426919db)
- Bug 1167962 - Fix import in synthesizeProfileForTest, r=orange (cc7fab771)
- fix merge of later patch Bug 1167006 (c0b57b0e2)
- Bug 1157523 - Fix intermittent where markers are selected in the waterfall views when there is no recording selected. r=vp (35cec0bd1)
- Bug 1196253 - update in-tree psutil to 3.1.1. r=gps (80f243738)
2021-05-26 11:17:55 +08:00

425 lines
12 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* 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";
/**
* This file contains the base line graph that all Performance line graphs use.
*/
const { Cc, Ci, Cu, Cr } = require("chrome");
const { Task } = require("resource://gre/modules/Task.jsm");
const { LineGraphWidget } = require("resource:///modules/devtools/Graphs.jsm");
const { BarGraphWidget } = require("resource:///modules/devtools/Graphs.jsm");
const { CanvasGraphUtils } = require("resource:///modules/devtools/Graphs.jsm");
const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
loader.lazyRequireGetter(this, "promise");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
loader.lazyRequireGetter(this, "colorUtils",
"devtools/css-color", true);
loader.lazyRequireGetter(this, "getColor",
"devtools/shared/theme", true);
loader.lazyRequireGetter(this, "ProfilerGlobal",
"devtools/performance/global");
loader.lazyRequireGetter(this, "TimelineGlobal",
"devtools/performance/global");
loader.lazyRequireGetter(this, "MarkersOverview",
"devtools/performance/markers-overview", true);
/**
* For line graphs
*/
const HEIGHT = 35; // px
const STROKE_WIDTH = 1; // px
const DAMPEN_VALUES = 0.95;
const CLIPHEAD_LINE_COLOR = "#666";
const SELECTION_LINE_COLOR = "#555";
const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
/**
* For timeline overview
*/
const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
/**
* A base class for performance graphs to inherit from.
*
* @param nsIDOMNode parent
* The parent node holding the overview.
* @param string metric
* The unit of measurement for this graph.
*/
function PerformanceGraph(parent, metric) {
LineGraphWidget.call(this, parent, { metric });
this.setTheme();
}
PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, {
strokeWidth: STROKE_WIDTH,
dampenValuesFactor: DAMPEN_VALUES,
fixedHeight: HEIGHT,
clipheadLineColor: CLIPHEAD_LINE_COLOR,
selectionLineColor: SELECTION_LINE_COLOR,
withTooltipArrows: false,
withFixedTooltipPositions: true,
/**
* Disables selection and empties this graph.
*/
clearView: function() {
this.selectionEnabled = false;
this.dropSelection();
this.setData([]);
},
/**
* Sets the theme via `theme` to either "light" or "dark",
* and updates the internal styling to match. Requires a redraw
* to see the effects.
*/
setTheme: function (theme) {
theme = theme || "light";
let mainColor = getColor(this.mainColor || "highlight-blue", theme);
this.backgroundColor = getColor("body-background", theme);
this.strokeColor = mainColor;
this.backgroundGradientStart = colorUtils.setAlpha(mainColor, 0.2);
this.backgroundGradientEnd = colorUtils.setAlpha(mainColor, 0.2);
this.selectionBackgroundColor = colorUtils.setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
this.maximumLineColor = colorUtils.setAlpha(mainColor, 0.4);
this.averageLineColor = colorUtils.setAlpha(mainColor, 0.7);
this.minimumLineColor = colorUtils.setAlpha(mainColor, 0.9);
}
});
/**
* Constructor for the framerate graph. Inherits from PerformanceGraph.
*
* @param nsIDOMNode parent
* The parent node holding the overview.
*/
function FramerateGraph(parent) {
PerformanceGraph.call(this, parent, ProfilerGlobal.L10N.getStr("graphs.fps"));
}
FramerateGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
mainColor: FRAMERATE_GRAPH_COLOR_NAME,
setPerformanceData: function ({ duration, ticks }, resolution) {
this.dataDuration = duration;
return this.setDataFromTimestamps(ticks, resolution);
}
});
/**
* Constructor for the memory graph. Inherits from PerformanceGraph.
*
* @param nsIDOMNode parent
* The parent node holding the overview.
*/
function MemoryGraph(parent) {
PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
}
MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
mainColor: MEMORY_GRAPH_COLOR_NAME,
setPerformanceData: function ({ duration, memory }) {
this.dataDuration = duration;
return this.setData(memory);
}
});
function TimelineGraph(parent, blueprint) {
MarkersOverview.call(this, parent, blueprint);
}
TimelineGraph.prototype = Heritage.extend(MarkersOverview.prototype, {
headerHeight: MARKERS_GRAPH_HEADER_HEIGHT,
rowHeight: MARKERS_GRAPH_ROW_HEIGHT,
groupPadding: MARKERS_GROUP_VERTICAL_PADDING,
setPerformanceData: MarkersOverview.prototype.setData
});
/**
* Definitions file for GraphsController, indicating the constructor,
* selector and other meta for each of the graphs controller by
* GraphsController.
*/
const GRAPH_DEFINITIONS = {
memory: {
constructor: MemoryGraph,
selector: "#memory-overview",
},
framerate: {
constructor: FramerateGraph,
selector: "#time-framerate",
},
timeline: {
constructor: TimelineGraph,
selector: "#markers-overview",
needsBlueprints: true,
primaryLink: true
}
};
/**
* A controller for orchestrating the performance's tool overview graphs. Constructs,
* syncs, toggles displays and defines the memory, framerate and timeline view.
*
* @param {object} definition
* @param {DOMElement} root
* @param {function} getBlueprint
* @param {function} getTheme
*/
function GraphsController ({ definition, root, getBlueprint, getTheme }) {
this._graphs = {};
this._enabled = new Set();
this._definition = definition || GRAPH_DEFINITIONS;
this._root = root;
this._getBlueprint = getBlueprint;
this._getTheme = getTheme;
this._primaryLink = Object.keys(this._definition).filter(name => this._definition[name].primaryLink)[0];
this.$ = root.ownerDocument.querySelector.bind(root.ownerDocument);
EventEmitter.decorate(this);
this._onSelecting = this._onSelecting.bind(this);
}
GraphsController.prototype = {
/**
* Returns the corresponding graph by `graphName`.
*/
get: function (graphName) {
return this._graphs[graphName];
},
/**
* Iterates through all graphs and renders the data
* from a RecordingModel. Takes a resolution value used in
* some graphs.
* Saves rendering progress as a promise to be consumed by `destroy`,
* to wait for cleaning up rendering during destruction.
*/
render: Task.async(function *(recordingData, resolution) {
// Get the previous render promise so we don't start rendering
// until the previous render cycle completes, which can occur
// especially when a recording is finished, and triggers a
// fresh rendering at a higher rate
yield (this._rendering && this._rendering.promise);
// Check after yielding to ensure we're not tearing down,
// as this can create a race condition in tests
if (this._destroyed) {
return;
}
this._rendering = promise.defer();
for (let graph of (yield this._getEnabled())) {
yield graph.setPerformanceData(recordingData, resolution);
this.emit("rendered", graph.graphName);
}
this._rendering.resolve();
}),
/**
* Destroys the underlying graphs.
*/
destroy: Task.async(function *() {
let primary = this._getPrimaryLink();
this._destroyed = true;
if (primary) {
primary.off("selecting", this._onSelecting);
}
// If there was rendering, wait until the most recent render cycle
// has finished
if (this._rendering) {
yield this._rendering.promise;
}
for (let graph of this.getWidgets()) {
yield graph.destroy();
}
}),
/**
* Applies the theme to the underlying graphs. Optionally takes
* a `redraw` boolean in the options to force redraw.
*/
setTheme: function (options={}) {
let theme = options.theme || this._getTheme();
for (let graph of this.getWidgets()) {
graph.setTheme(theme);
graph.refresh({ force: options.redraw });
}
},
/**
* Sets up the graph, if needed. Returns a promise resolving
* to the graph if it is enabled once it's ready, or otherwise returns
* null if disabled.
*/
isAvailable: Task.async(function *(graphName) {
if (!this._enabled.has(graphName)) {
return null;
}
let graph = this.get(graphName);
if (!graph) {
graph = yield this._construct(graphName);
}
yield graph.ready();
return graph;
}),
/**
* Enable or disable a subgraph controlled by GraphsController.
* This determines what graphs are visible and get rendered.
*/
enable: function (graphName, isEnabled) {
let el = this.$(this._definition[graphName].selector);
el.hidden = !isEnabled;
// If no status change, just return
if (this._enabled.has(graphName) === isEnabled) {
return;
}
if (isEnabled) {
this._enabled.add(graphName);
} else {
this._enabled.delete(graphName);
}
// Invalidate our cache of ready-to-go graphs
this._enabledGraphs = null;
},
/**
* Disables all graphs controller by the GraphsController, and
* also hides the root element. This is a one way switch, and used
* when older platforms do not have any timeline data.
*/
disableAll: function () {
this._root.hidden = true;
// Hide all the subelements
Object.keys(this._definition).forEach(graphName => this.enable(graphName, false));
},
/**
* Sets a mapped selection on the graph that is the main controller
* for keeping the graphs' selections in sync.
*/
setMappedSelection: function (selection, { mapStart, mapEnd }) {
return this._getPrimaryLink().setMappedSelection(selection, { mapStart, mapEnd });
},
getMappedSelection: function ({ mapStart, mapEnd }) {
if (this._getPrimaryLink()) {
return this._getPrimaryLink().getMappedSelection({ mapStart, mapEnd });
} else {
return null;
}
},
/**
* Returns an array of graphs that have been created, not necessarily
* enabled currently.
*/
getWidgets: function () {
return Object.keys(this._graphs).map(name => this._graphs[name]);
},
/**
* Drops the selection.
*/
dropSelection: function () {
if (this._getPrimaryLink()) {
return this._getPrimaryLink().dropSelection();
}
},
/**
* Makes sure the selection is enabled or disabled in all the graphs.
*/
selectionEnabled: Task.async(function *(enabled) {
for (let graph of (yield this._getEnabled())) {
graph.selectionEnabled = enabled;
}
}),
/**
* Creates the graph `graphName` and initializes it.
*/
_construct: Task.async(function *(graphName) {
let def = this._definition[graphName];
let el = this.$(def.selector);
let blueprint = def.needsBlueprints ? this._getBlueprint() : void 0;
let graph = this._graphs[graphName] = new def.constructor(el, blueprint);
graph.graphName = graphName;
yield graph.ready();
// Sync the graphs' animations and selections together
if (def.primaryLink) {
graph.on("selecting", this._onSelecting);
} else {
CanvasGraphUtils.linkAnimation(this._getPrimaryLink(), graph);
CanvasGraphUtils.linkSelection(this._getPrimaryLink(), graph);
}
this.setTheme();
return graph;
}),
/**
* Returns the main graph for this collection, that all graphs
* are bound to for syncing and selection.
*/
_getPrimaryLink: function () {
return this.get(this._primaryLink);
},
/**
* Emitted when a selection occurs.
*/
_onSelecting: function () {
this.emit("selecting");
},
/**
* Resolves to an array with all graphs that are enabled, and
* creates them if needed. Different than just iterating over `this._graphs`,
* as those could be enabled. Uses caching, as rendering happens many times per second,
* compared to how often which graphs/features are changed (rarely).
*/
_getEnabled: Task.async(function *() {
if (this._enabledGraphs) {
return this._enabledGraphs;
}
let enabled = [];
for (let graphName of this._enabled) {
let graph;
if (graph = yield this.isAvailable(graphName)) {
enabled.push(graph);
}
}
return this._enabledGraphs = enabled;
}),
};
exports.FramerateGraph = FramerateGraph;
exports.MemoryGraph = MemoryGraph;
exports.TimelineGraph = TimelineGraph;
exports.GraphsController = GraphsController;