Bug 1150299 - Show optimization graph in the call tree sidebar. r=shu,vp

This commit is contained in:
Jordan Santell 2015-08-31 15:26:02 -07:00
parent f88d1a3d3d
commit b8b0b9811d
11 changed files with 266 additions and 93 deletions

View File

@ -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;

View File

@ -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 = [];
}

View File

@ -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;
}
};

View File

@ -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;

View File

@ -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",

View File

@ -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>

View File

@ -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) {

View File

@ -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();
}

View File

@ -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]"
};

View File

@ -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);

View File

@ -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;