mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-04 11:26:09 +00:00
Bug 1150299 - Show optimization graph in the call tree sidebar. r=shu,vp
This commit is contained in:
parent
f88d1a3d3d
commit
b8b0b9811d
@ -554,6 +554,30 @@ function getFrameInfo (node, options) {
|
||||
}
|
||||
|
||||
exports.getFrameInfo = getFrameInfo;
|
||||
|
||||
/**
|
||||
* Takes an inverted ThreadNode and searches its youngest frames for
|
||||
* a FrameNode with matching location.
|
||||
*
|
||||
* @param {ThreadNode} threadNode
|
||||
* @param {string} location
|
||||
* @return {?FrameNode}
|
||||
*/
|
||||
function findFrameByLocation (threadNode, location) {
|
||||
if (!threadNode.inverted) {
|
||||
throw new Error("FrameUtils.findFrameByLocation only supports leaf nodes in an inverted tree.");
|
||||
}
|
||||
|
||||
let calls = threadNode.calls;
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
if (calls[i].location === location) {
|
||||
return calls[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
exports.findFrameByLocation = findFrameByLocation;
|
||||
exports.computeIsContentAndCategory = computeIsContentAndCategory;
|
||||
exports.parseLocation = parseLocation;
|
||||
exports.getInflatedFrameCache = getInflatedFrameCache;
|
||||
|
@ -269,23 +269,13 @@ const IMPLEMENTATION_NAMES = Object.keys(IMPLEMENTATION_MAP);
|
||||
* @param {Array<number>} sampleTimes
|
||||
* An array of every sample time within the range we're counting.
|
||||
* From a ThreadNode's `sampleTimes` property.
|
||||
* @param {number} op.startTime
|
||||
* The start time of the first sample.
|
||||
* @param {number} op.endTime
|
||||
* The end time of the last sample.
|
||||
* @param {number} op.resolution
|
||||
* The maximum amount of possible data points returned.
|
||||
* Also determines the size in milliseconds of each bucket
|
||||
* via `(endTime - startTime) / resolution`
|
||||
* @param {number} bucketSize
|
||||
* Size of each bucket in milliseconds.
|
||||
* `duration / resolution = bucketSize` in OptimizationsGraph.
|
||||
* @return {?Array<object>}
|
||||
*/
|
||||
function createTierGraphDataFromFrameNode (frameNode, sampleTimes, { startTime, endTime, resolution }) {
|
||||
if (!frameNode.hasOptimizations()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tierData = frameNode.getOptimizationTierData();
|
||||
let duration = endTime - startTime;
|
||||
function createTierGraphDataFromFrameNode (frameNode, sampleTimes, bucketSize) {
|
||||
let tierData = frameNode.getTierData();
|
||||
let stringTable = frameNode._stringTable;
|
||||
let output = [];
|
||||
let implEnum;
|
||||
@ -297,8 +287,9 @@ function createTierGraphDataFromFrameNode (frameNode, sampleTimes, { startTime,
|
||||
let samplesInCurrentBucket = 0;
|
||||
let currentBucketStartTime = sampleTimes[0];
|
||||
let bucket = [];
|
||||
// Size of each bucket in milliseconds
|
||||
let bucketSize = Math.ceil(duration / resolution);
|
||||
|
||||
// Store previous data point so we can have straight vertical lines
|
||||
let previousValues;
|
||||
|
||||
// Iterate one after the samples, so we can finalize the last bucket
|
||||
for (let i = 0; i <= sampleTimes.length; i++) {
|
||||
@ -310,19 +301,30 @@ function createTierGraphDataFromFrameNode (frameNode, sampleTimes, { startTime,
|
||||
i >= sampleTimes.length) {
|
||||
|
||||
let dataPoint = {};
|
||||
dataPoint.ys = [];
|
||||
dataPoint.x = currentBucketStartTime;
|
||||
dataPoint.values = [];
|
||||
dataPoint.delta = currentBucketStartTime;
|
||||
|
||||
// Map the opt site counts as a normalized percentage (0-1)
|
||||
// of its count in context of total samples this bucket
|
||||
for (let j = 0; j < IMPLEMENTATION_NAMES.length; j++) {
|
||||
dataPoint.ys[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1);
|
||||
dataPoint.values[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1);
|
||||
}
|
||||
|
||||
// Push the values from the previous bucket to the same time
|
||||
// as the current bucket so we get a straight vertical line.
|
||||
if (previousValues) {
|
||||
let data = Object.create(null);
|
||||
data.values = previousValues;
|
||||
data.delta = currentBucketStartTime;
|
||||
output.push(data);
|
||||
}
|
||||
|
||||
output.push(dataPoint);
|
||||
|
||||
// Set the new start time of this bucket and reset its count
|
||||
currentBucketStartTime += bucketSize;
|
||||
samplesInCurrentBucket = 0;
|
||||
previousValues = dataPoint.values;
|
||||
bucket = [];
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,8 @@ function ThreadNode(thread, options = {}) {
|
||||
this.calls = [];
|
||||
this.duration = options.endTime - options.startTime;
|
||||
this.nodeType = "Thread";
|
||||
this.inverted = options.invertTree;
|
||||
|
||||
// Total bytesize of all allocations if enabled
|
||||
this.byteSize = 0;
|
||||
this.youngestFrameByteSize = 0;
|
||||
@ -232,10 +234,8 @@ ThreadNode.prototype = {
|
||||
leafTable);
|
||||
if (isLeaf) {
|
||||
frameNode.youngestFrameSamples++;
|
||||
if (inflatedFrame.optimizations) {
|
||||
frameNode._addOptimizations(inflatedFrame.optimizations, inflatedFrame.implementation,
|
||||
sampleTime, stringTable);
|
||||
}
|
||||
frameNode._addOptimizations(inflatedFrame.optimizations, inflatedFrame.implementation,
|
||||
sampleTime, stringTable);
|
||||
|
||||
if (byteSize) {
|
||||
frameNode.youngestFrameByteSize += byteSize;
|
||||
@ -410,7 +410,7 @@ function FrameNode(frameKey, { location, line, category, isContent }, isMetaCate
|
||||
this.calls = [];
|
||||
this.isContent = !!isContent;
|
||||
this._optimizations = null;
|
||||
this._tierData = null;
|
||||
this._tierData = [];
|
||||
this._stringTable = null;
|
||||
this.isMetaCategory = !!isMetaCategory;
|
||||
this.category = category;
|
||||
@ -427,8 +427,8 @@ FrameNode.prototype = {
|
||||
* Any JIT optimization information attached to the current
|
||||
* sample. Lazily inflated via stringTable.
|
||||
* @param number implementation
|
||||
* JIT implementation used for this observed frame (interpreter,
|
||||
* baseline, ion);
|
||||
* JIT implementation used for this observed frame (baseline, ion);
|
||||
* can be null indicating "interpreter"
|
||||
* @param number time
|
||||
* The time this optimization occurred.
|
||||
* @param object stringTable
|
||||
@ -441,16 +441,16 @@ FrameNode.prototype = {
|
||||
let opts = this._optimizations;
|
||||
if (opts === null) {
|
||||
opts = this._optimizations = [];
|
||||
this._stringTable = stringTable;
|
||||
}
|
||||
opts.push(site);
|
||||
|
||||
if (this._tierData === null) {
|
||||
this._tierData = [];
|
||||
}
|
||||
// Record type of implementation used and the sample time
|
||||
this._tierData.push({ implementation, time });
|
||||
}
|
||||
|
||||
if (!this._stringTable) {
|
||||
this._stringTable = stringTable;
|
||||
}
|
||||
|
||||
// Record type of implementation used and the sample time
|
||||
this._tierData.push({ implementation, time });
|
||||
},
|
||||
|
||||
_clone: function (samples, size) {
|
||||
@ -474,17 +474,29 @@ FrameNode.prototype = {
|
||||
this.youngestFrameByteSize += otherNode.youngestFrameByteSize;
|
||||
}
|
||||
|
||||
if (this._stringTable === null) {
|
||||
this._stringTable = otherNode._stringTable;
|
||||
}
|
||||
|
||||
if (otherNode._optimizations) {
|
||||
let opts = this._optimizations;
|
||||
if (opts === null) {
|
||||
opts = this._optimizations = [];
|
||||
this._stringTable = otherNode._stringTable;
|
||||
if (!this._optimizations) {
|
||||
this._optimizations = [];
|
||||
}
|
||||
let opts = this._optimizations;
|
||||
let otherOpts = otherNode._optimizations;
|
||||
for (let i = 0; i < otherOpts.length; i++) {
|
||||
opts.push(otherOpts[i]);
|
||||
opts.push(otherOpts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (otherNode._tierData.length) {
|
||||
let tierData = this._tierData;
|
||||
let otherTierData = otherNode._tierData;
|
||||
for (let i = 0; i < otherTierData.length; i++) {
|
||||
tierData.push(otherTierData[i]);
|
||||
}
|
||||
tierData.sort((a, b) => a.time - b.time);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -529,14 +541,11 @@ FrameNode.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the optimization tiers used overtime.
|
||||
* Returns the tiers used overtime.
|
||||
*
|
||||
* @return {?Array<object>}
|
||||
* @return {Array<object>}
|
||||
*/
|
||||
getOptimizationTierData: function () {
|
||||
if (!this._tierData) {
|
||||
return null;
|
||||
}
|
||||
getTierData: function () {
|
||||
return this._tierData;
|
||||
}
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const LineGraphWidget = require("devtools/shared/widgets/LineGraphWidget");
|
||||
const BarGraphWidget = require("devtools/shared/widgets/BarGraphWidget");
|
||||
const MountainGraphWidget = require("devtools/shared/widgets/MountainGraphWidget");
|
||||
const { CanvasGraphUtils } = require("devtools/shared/widgets/Graphs");
|
||||
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
@ -28,6 +29,8 @@ loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "MarkersOverview",
|
||||
"devtools/performance/markers-overview", true);
|
||||
loader.lazyRequireGetter(this, "createTierGraphDataFromFrameNode",
|
||||
"devtools/performance/jit", true);
|
||||
|
||||
/**
|
||||
* For line graphs
|
||||
@ -37,9 +40,9 @@ 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";
|
||||
const SELECTION_BACKGROUND_COLOR_NAME = "graphs-blue";
|
||||
const FRAMERATE_GRAPH_COLOR_NAME = "graphs-green";
|
||||
const MEMORY_GRAPH_COLOR_NAME = "graphs-blue";
|
||||
|
||||
/**
|
||||
* For timeline overview
|
||||
@ -48,6 +51,11 @@ const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
|
||||
const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
|
||||
const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
|
||||
|
||||
/**
|
||||
* For optimization graph
|
||||
*/
|
||||
const OPTIMIZATIONS_GRAPH_RESOLUTION = 100;
|
||||
|
||||
/**
|
||||
* A base class for performance graphs to inherit from.
|
||||
*
|
||||
@ -86,7 +94,7 @@ PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, {
|
||||
*/
|
||||
setTheme: function (theme) {
|
||||
theme = theme || "light";
|
||||
let mainColor = getColor(this.mainColor || "highlight-blue", theme);
|
||||
let mainColor = getColor(this.mainColor || "graphs-blue", theme);
|
||||
this.backgroundColor = getColor("body-background", theme);
|
||||
this.strokeColor = mainColor;
|
||||
this.backgroundGradientStart = colorUtils.setAlpha(mainColor, 0.2);
|
||||
@ -425,6 +433,82 @@ GraphsController.prototype = {
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 OptimizationsGraph(parent) {
|
||||
MountainGraphWidget.call(this, parent);
|
||||
this.setTheme();
|
||||
}
|
||||
|
||||
OptimizationsGraph.prototype = Heritage.extend(MountainGraphWidget.prototype, {
|
||||
|
||||
render: Task.async(function *(threadNode, frameNode) {
|
||||
// Regardless if we draw or clear the graph, wait
|
||||
// until it's ready.
|
||||
yield this.ready();
|
||||
|
||||
if (!threadNode || !frameNode) {
|
||||
this.setData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
let { sampleTimes } = threadNode;
|
||||
|
||||
if (!sampleTimes.length) {
|
||||
this.setData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Take startTime/endTime from samples recorded, rather than
|
||||
// using duration directly from threadNode, as the first sample that
|
||||
// equals the startTime does not get recorded.
|
||||
let startTime = sampleTimes[0];
|
||||
let endTime = sampleTimes[sampleTimes.length - 1];
|
||||
|
||||
let bucketSize = (endTime - startTime) / OPTIMIZATIONS_GRAPH_RESOLUTION;
|
||||
let data = createTierGraphDataFromFrameNode(frameNode, sampleTimes, bucketSize);
|
||||
|
||||
// If for some reason we don't have data (like the frameNode doesn't
|
||||
// have optimizations, but it shouldn't be at this point if it doesn't),
|
||||
// log an error.
|
||||
if (!data) {
|
||||
Cu.reportError(`FrameNode#${frameNode.location} does not have optimizations data to render.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataOffsetX = startTime;
|
||||
yield this.setData(data);
|
||||
}),
|
||||
|
||||
/**
|
||||
* 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 interpreterColor = getColor("graphs-red", theme);
|
||||
let baselineColor = getColor("graphs-blue", theme);
|
||||
let ionColor = getColor("graphs-green", theme);
|
||||
|
||||
this.format = [
|
||||
{ color: interpreterColor },
|
||||
{ color: baselineColor },
|
||||
{ color: ionColor },
|
||||
];
|
||||
|
||||
this.backgroundColor = getColor("sidebar-background", theme);
|
||||
}
|
||||
});
|
||||
|
||||
exports.OptimizationsGraph = OptimizationsGraph;
|
||||
exports.FramerateGraph = FramerateGraph;
|
||||
exports.MemoryGraph = MemoryGraph;
|
||||
exports.TimelineGraph = TimelineGraph;
|
||||
|
@ -33,6 +33,8 @@ loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/toolkit/performance/utils");
|
||||
loader.lazyRequireGetter(this, "GraphsController",
|
||||
"devtools/performance/graphs", true);
|
||||
loader.lazyRequireGetter(this, "OptimizationsGraph",
|
||||
"devtools/performance/graphs", true);
|
||||
loader.lazyRequireGetter(this, "WaterfallHeader",
|
||||
"devtools/performance/waterfall-ticks", true);
|
||||
loader.lazyRequireGetter(this, "MarkerView",
|
||||
@ -43,6 +45,8 @@ loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
"devtools/performance/marker-utils");
|
||||
loader.lazyRequireGetter(this, "WaterfallUtils",
|
||||
"devtools/performance/waterfall-utils");
|
||||
loader.lazyRequireGetter(this, "FrameUtils",
|
||||
"devtools/performance/frame-utils");
|
||||
loader.lazyRequireGetter(this, "CallView",
|
||||
"devtools/performance/tree-view", true);
|
||||
loader.lazyRequireGetter(this, "ThreadNode",
|
||||
|
@ -77,7 +77,7 @@
|
||||
</menupopup>
|
||||
</popupset>
|
||||
|
||||
<hbox class="theme-body" flex="1">
|
||||
<hbox id="body" class="theme-body" flex="1">
|
||||
|
||||
<!-- Sidebar: controls and recording list -->
|
||||
<vbox id="recordings-pane">
|
||||
@ -304,6 +304,7 @@
|
||||
<span class="header-line opt-line" />
|
||||
</hbox>
|
||||
</toolbar>
|
||||
<hbox id="optimizations-graph"></hbox>
|
||||
<vbox id="jit-optimizations-raw-view"></vbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
@ -364,8 +365,7 @@
|
||||
</vbox>
|
||||
|
||||
<!-- Memory FlameChart -->
|
||||
<hbox id="memory-flamegraph-view" flex="1">
|
||||
</hbox>
|
||||
<hbox id="memory-flamegraph-view" flex="1"></hbox>
|
||||
</deck>
|
||||
</deck>
|
||||
</vbox>
|
||||
|
@ -36,27 +36,41 @@ add_task(function test() {
|
||||
equal(root.sampleTimes[root.sampleTimes.length - 1], endTime, "root recorded last sample time in scope");
|
||||
|
||||
let frame = getFrameNodePath(root, "X");
|
||||
let data = createTierGraphDataFromFrameNode(frame, root.sampleTimes, { startTime, endTime, resolution: RESOLUTION });
|
||||
let data = createTierGraphDataFromFrameNode(frame, root.sampleTimes, (endTime-startTime)/RESOLUTION);
|
||||
|
||||
let TIME_PER_WINDOW = SAMPLE_COUNT / 2 / RESOLUTION * TIME_PER_SAMPLE;
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "first window has correct x");
|
||||
equal(data[i].ys[0], 0.2, "first window has 2 frames in interpreter");
|
||||
equal(data[i].ys[1], 0.2, "first window has 2 frames in baseline");
|
||||
equal(data[i].ys[2], 0.2, "first window has 2 frames in ion");
|
||||
// Filter out the dupes created with the same delta so the graph
|
||||
// can render correctly.
|
||||
let filteredData = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (!i || data[i].delta !== data[i-1].delta) {
|
||||
filteredData.push(data[i]);
|
||||
}
|
||||
}
|
||||
for (let i = 10; i < 20; i++) {
|
||||
equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "second window has correct x");
|
||||
equal(data[i].ys[0], 0, "second window observed no optimizations");
|
||||
equal(data[i].ys[1], 0, "second window observed no optimizations");
|
||||
equal(data[i].ys[2], 0, "second window observed no optimizations");
|
||||
data = filteredData;
|
||||
|
||||
for (let i = 0; i < 11; i++) {
|
||||
equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "first window has correct x");
|
||||
equal(data[i].values[0], 0.2, "first window has 2 frames in interpreter");
|
||||
equal(data[i].values[1], 0.2, "first window has 2 frames in baseline");
|
||||
equal(data[i].values[2], 0.2, "first window has 2 frames in ion");
|
||||
}
|
||||
for (let i = 20; i < 30; i++) {
|
||||
equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "third window has correct x");
|
||||
equal(data[i].ys[0], 0.3, "third window has 3 frames in interpreter");
|
||||
equal(data[i].ys[1], 0, "third window has 0 frames in baseline");
|
||||
equal(data[i].ys[2], 0, "third window has 0 frames in ion");
|
||||
// Start on 11, since i===10 is where the values change, and the new value (0,0,0)
|
||||
// is removed in `filteredData`
|
||||
for (let i = 11; i < 20; i++) {
|
||||
equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "second window has correct x");
|
||||
equal(data[i].values[0], 0, "second window observed no optimizations");
|
||||
equal(data[i].values[1], 0, "second window observed no optimizations");
|
||||
equal(data[i].values[2], 0, "second window observed no optimizations");
|
||||
}
|
||||
// Start on 21, since i===20 is where the values change, and the new value (0.3,0,0)
|
||||
// is removed in `filteredData`
|
||||
for (let i = 21; i < 30; i++) {
|
||||
equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "third window has correct x");
|
||||
equal(data[i].values[0], 0.3, "third window has 3 frames in interpreter");
|
||||
equal(data[i].values[1], 0, "third window has 0 frames in baseline");
|
||||
equal(data[i].values[2], 0, "third window has 0 frames in ion");
|
||||
}
|
||||
});
|
||||
|
||||
@ -68,20 +82,19 @@ function uniqStr(s) {
|
||||
|
||||
const TIER_PATTERNS = [
|
||||
// 0-99
|
||||
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
|
||||
["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
|
||||
// 100-199
|
||||
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
|
||||
["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
|
||||
// 200-299
|
||||
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
|
||||
["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
|
||||
// 300-399
|
||||
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
|
||||
["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
|
||||
// 400-499
|
||||
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
|
||||
["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
|
||||
|
||||
// 500-599
|
||||
// Test current frames in all opts, including that
|
||||
// the same frame with no opts does not get counted
|
||||
["X", "X", "A", "A", "X_1", "X_2", "X_1", "X_2", "X_0", "X_0"],
|
||||
// Test current frames in all opts
|
||||
["A", "A", "A", "A", "X_1", "X_2", "X_1", "X_2", "X_0", "X_0"],
|
||||
|
||||
// 600-699
|
||||
// Nothing for current frame
|
||||
@ -92,9 +105,9 @@ const TIER_PATTERNS = [
|
||||
["X_2 -> Y", "X_2 -> Y", "X_2 -> Y", "X_0", "X_0", "X_0", "A", "A", "A", "A"],
|
||||
|
||||
// 800-899
|
||||
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
|
||||
["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
|
||||
// 900-999
|
||||
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
|
||||
["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
|
||||
];
|
||||
|
||||
function createSample (i, frames) {
|
||||
|
@ -36,6 +36,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
||||
destroy: function () {
|
||||
OptimizationsListView.destroy();
|
||||
this.container = null;
|
||||
this.threadNode = null;
|
||||
DetailsSubview.destroy.call(this);
|
||||
},
|
||||
|
||||
@ -56,7 +57,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
||||
flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
|
||||
showOptimizationHint: optimizations
|
||||
};
|
||||
let threadNode = this._prepareCallTree(profile, interval, options);
|
||||
let threadNode = this.threadNode = this._prepareCallTree(profile, interval, options);
|
||||
this._populateCallTree(threadNode, options);
|
||||
|
||||
if (optimizations) {
|
||||
@ -79,7 +80,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
||||
|
||||
_onFocus: function (_, treeItem) {
|
||||
if (PerformanceController.getCurrentRecording().getConfiguration().withJITOptimizations) {
|
||||
OptimizationsListView.setCurrentFrame(treeItem.frame);
|
||||
OptimizationsListView.setCurrentFrame(this.threadNode, treeItem.frame);
|
||||
OptimizationsListView.render();
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ let OptimizationsListView = {
|
||||
*/
|
||||
initialize: function () {
|
||||
this.reset = this.reset.bind(this);
|
||||
this._onThemeChanged = this._onThemeChanged.bind(this);
|
||||
|
||||
this.el = $("#jit-optimizations-view");
|
||||
this.$headerName = $("#jit-optimizations-header .header-function-name");
|
||||
@ -34,15 +35,20 @@ let OptimizationsListView = {
|
||||
sorted: false,
|
||||
emptyText: JIT_EMPTY_TEXT
|
||||
});
|
||||
this.graph = new OptimizationsGraph($("#optimizations-graph"));
|
||||
this.graph.setTheme(PerformanceController.getTheme());
|
||||
|
||||
// Start the tree by resetting.
|
||||
this.reset();
|
||||
|
||||
PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function called when the tool cleans up.
|
||||
*/
|
||||
destroy: function () {
|
||||
PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
||||
this.tree = null;
|
||||
this.$headerName = this.$headerFile = this.$headerLine = this.el = null;
|
||||
},
|
||||
@ -53,7 +59,10 @@ let OptimizationsListView = {
|
||||
*
|
||||
* @param {FrameNode} frameNode
|
||||
*/
|
||||
setCurrentFrame: function (frameNode) {
|
||||
setCurrentFrame: function (threadNode, frameNode) {
|
||||
if (threadNode !== this.getCurrentThread()) {
|
||||
this._currentThread = threadNode;
|
||||
}
|
||||
if (frameNode !== this.getCurrentFrame()) {
|
||||
this._currentFrame = frameNode;
|
||||
}
|
||||
@ -64,16 +73,25 @@ let OptimizationsListView = {
|
||||
*
|
||||
* @return {?FrameNode}
|
||||
*/
|
||||
getCurrentFrame: function (frameNode) {
|
||||
getCurrentFrame: function () {
|
||||
return this._currentFrame;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current thread node for this view.
|
||||
*
|
||||
* @return {?ThreadNode}
|
||||
*/
|
||||
getCurrentThread: function () {
|
||||
return this._currentThread;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears out data in the tree, sets to an empty state,
|
||||
* and removes current frame.
|
||||
*/
|
||||
reset: function () {
|
||||
this.setCurrentFrame(null);
|
||||
this.setCurrentFrame(null, null);
|
||||
this.clear();
|
||||
this.el.classList.add("empty");
|
||||
this.emit(EVENTS.OPTIMIZATIONS_RESET);
|
||||
@ -123,9 +141,18 @@ let OptimizationsListView = {
|
||||
this._renderSite(view, site, frameData);
|
||||
}
|
||||
|
||||
this._renderTierGraph();
|
||||
|
||||
this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame());
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the optimization tier graph over time.
|
||||
*/
|
||||
_renderTierGraph: function () {
|
||||
this.graph.render(this.getCurrentThread(), this.getCurrentFrame());
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an entry in the tree widget for an optimization site.
|
||||
*/
|
||||
@ -175,7 +202,6 @@ let OptimizationsListView = {
|
||||
/**
|
||||
* Creates an element for insertion in the raw view for an OptimizationSite.
|
||||
*/
|
||||
|
||||
_createSiteNode: function (frameData, site) {
|
||||
let node = document.createElement("span");
|
||||
let desc = document.createElement("span");
|
||||
@ -225,7 +251,6 @@ let OptimizationsListView = {
|
||||
* @param {IonType} ionType
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
_createIonNode: function (ionType) {
|
||||
let node = document.createElement("span");
|
||||
node.textContent = `${ionType.site} : ${ionType.mirType}`;
|
||||
@ -240,7 +265,6 @@ let OptimizationsListView = {
|
||||
* @param {ObservedType} type
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
_createObservedTypeNode: function (type) {
|
||||
let node = document.createElement("span");
|
||||
let typeNode = document.createElement("span");
|
||||
@ -280,7 +304,6 @@ let OptimizationsListView = {
|
||||
* @param {OptimizationAttempt} attempt
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
_createAttemptNode: function (attempt) {
|
||||
let node = document.createElement("span");
|
||||
let strategyNode = document.createElement("span");
|
||||
@ -309,7 +332,6 @@ let OptimizationsListView = {
|
||||
* @param {?Element} el
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
_createDebuggerLinkNode: function (url, line, el) {
|
||||
let node = el || document.createElement("span");
|
||||
node.className = "opt-url";
|
||||
@ -329,7 +351,6 @@ let OptimizationsListView = {
|
||||
/**
|
||||
* Updates the headers with the current frame's data.
|
||||
*/
|
||||
|
||||
_setHeaders: function (frameData) {
|
||||
let isMeta = frameData.isMetaCategory;
|
||||
let name = isMeta ? frameData.categoryData.label : frameData.functionName;
|
||||
@ -351,7 +372,6 @@ let OptimizationsListView = {
|
||||
* @param {String} url
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
_isLinkableURL: function (url) {
|
||||
return url && url.indexOf &&
|
||||
(url.indexOf("http") === 0 ||
|
||||
@ -359,6 +379,14 @@ let OptimizationsListView = {
|
||||
url.indexOf("file://") === 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when `devtools.theme` changes.
|
||||
*/
|
||||
_onThemeChanged: function (_, theme) {
|
||||
this.graph.setTheme(theme);
|
||||
this.graph.refresh({ force: true });
|
||||
},
|
||||
|
||||
toString: () => "[object OptimizationsListView]"
|
||||
};
|
||||
|
||||
|
@ -12,7 +12,7 @@ const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const GRAPH_DAMPEN_VALUES_FACTOR = 0.9;
|
||||
|
||||
const GRAPH_BACKGROUND_COLOR = "#ddd";
|
||||
const GRAPH_STROKE_WIDTH = 2; // px
|
||||
const GRAPH_STROKE_WIDTH = 1; // px
|
||||
const GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
|
||||
const GRAPH_HELPER_LINES_DASH = [5]; // px
|
||||
const GRAPH_HELPER_LINES_WIDTH = 1; // px
|
||||
@ -125,8 +125,8 @@ MountainGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
|
||||
let totalSections = this.format.length;
|
||||
let totalTicks = this._data.length;
|
||||
let firstTick = this._data[0].delta;
|
||||
let lastTick = this._data[totalTicks - 1].delta;
|
||||
let firstTick = totalTicks ? this._data[0].delta : 0;
|
||||
let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
|
||||
|
||||
let duration = this.dataDuration || lastTick;
|
||||
let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
|
||||
|
@ -589,6 +589,14 @@ menuitem.marker-color-graphs-grey:before,
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
#optimizations-graph {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#jit-optimizations-view.empty #optimizations-graph {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* override default styles for tree widget */
|
||||
#jit-optimizations-view .tree-widget-empty-text {
|
||||
font-size: inherit;
|
||||
|
Loading…
Reference in New Issue
Block a user