зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1150295 - Display an icon next to frames in the call tree that contain viewable optimization data. r=vp,a=kwierso
This commit is contained in:
Родитель
4cf955fbee
Коммит
27921a0d16
|
@ -459,6 +459,7 @@ FrameNode.prototype = {
|
|||
parsedData.categoryData = categoryData;
|
||||
parsedData.isContent = this.isContent;
|
||||
parsedData.isMetaCategory = this.isMetaCategory;
|
||||
parsedData.hasOptimizations = this.hasOptimizations();
|
||||
|
||||
return this._data = parsedData;
|
||||
},
|
||||
|
|
|
@ -16,6 +16,8 @@ const { AbstractTreeItem } = require("resource:///modules/devtools/AbstractTreeI
|
|||
const MILLISECOND_UNITS = L10N.getStr("table.ms");
|
||||
const PERCENTAGE_UNITS = L10N.getStr("table.percentage");
|
||||
const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
|
||||
const VIEW_OPTIMIZATIONS_TOOLTIP = L10N.getStr("table.view-optimizations.tooltiptext");
|
||||
|
||||
const CALL_TREE_INDENTATION = 16; // px
|
||||
|
||||
const DEFAULT_SORTING_PREDICATE = (frameA, frameB) => {
|
||||
|
@ -85,10 +87,14 @@ const sum = vals => vals.reduce((a, b) => a + b, 0);
|
|||
* An object specifying which cells are visible in the tree. Defaults to
|
||||
* the caller's `visibleCells` if a caller exists, otherwise defaults
|
||||
* to DEFAULT_VISIBLE_CELLS.
|
||||
* @param boolean showOptimizationHint [optional]
|
||||
* Whether or not to show an icon indicating if the frame has optimization
|
||||
* data.
|
||||
*/
|
||||
function CallView({
|
||||
caller, frame, level, hidden, inverted,
|
||||
sortingPredicate, autoExpandDepth, visibleCells
|
||||
sortingPredicate, autoExpandDepth, visibleCells,
|
||||
showOptimizationHint
|
||||
}) {
|
||||
AbstractTreeItem.call(this, {
|
||||
parent: caller,
|
||||
|
@ -114,6 +120,7 @@ function CallView({
|
|||
this.frame = frame;
|
||||
this.hidden = hidden;
|
||||
this.inverted = inverted;
|
||||
this.showOptimizationHint = showOptimizationHint;
|
||||
|
||||
this._onUrlClick = this._onUrlClick.bind(this);
|
||||
};
|
||||
|
@ -256,6 +263,16 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
|||
cell.setAttribute("type", "function");
|
||||
cell.appendChild(arrowNode);
|
||||
|
||||
// Render optimization link to JIT view if the frame
|
||||
// has optimizations
|
||||
if (this.root.showOptimizationHint && frameInfo.hasOptimizations && !frameInfo.isMetaCategory) {
|
||||
let icon = doc.createElement("description");
|
||||
icon.setAttribute("tooltiptext", VIEW_OPTIMIZATIONS_TOOLTIP);
|
||||
icon.setAttribute("type", "linkable");
|
||||
icon.className = "opt-icon";
|
||||
cell.appendChild(icon);
|
||||
}
|
||||
|
||||
// Don't render a name label node if there's no function name. A different
|
||||
// location label node will be rendered instead.
|
||||
if (frameName) {
|
||||
|
@ -280,6 +297,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
|||
|
||||
return cell;
|
||||
},
|
||||
|
||||
_appendFunctionDetailsCells: function(doc, cell, frameInfo) {
|
||||
if (frameInfo.fileName) {
|
||||
let urlNode = doc.createElement("description");
|
||||
|
|
|
@ -137,6 +137,7 @@ skip-if = os == 'linux' # Bug 1172120
|
|||
[browser_profiler_tree-view-08.js]
|
||||
[browser_profiler_tree-view-09.js]
|
||||
[browser_profiler_tree-view-10.js]
|
||||
[browser_profiler_tree-view-11.js]
|
||||
[browser_timeline-filters-01.js]
|
||||
[browser_timeline-filters-02.js]
|
||||
[browser_timeline-waterfall-background.js]
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that if a recording set `withJITOptimizations` on, then an
|
||||
* icon is next to the frame with optimizations
|
||||
*/
|
||||
|
||||
const RecordingUtils = devtools.require("devtools/performance/recording-utils");
|
||||
const { CATEGORY_MASK } = devtools.require("devtools/performance/global");
|
||||
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
|
||||
let { OverviewView, DetailsView, JITOptimizationsView, JsCallTreeView, RecordingsView } = panel.panelWin;
|
||||
|
||||
let profilerData = { threads: [gThread] };
|
||||
|
||||
Services.prefs.setBoolPref(JIT_PREF, true);
|
||||
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
|
||||
Services.prefs.setBoolPref(INVERT_PREF, false);
|
||||
|
||||
// Make two recordings, so we have one to switch to later, as the
|
||||
// second one will have fake sample data
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
|
||||
yield injectAndRenderProfilerData();
|
||||
|
||||
let rows = $$("#js-calltree-view .call-tree-item");
|
||||
is(rows.length, 4, "4 call tree rows exist");
|
||||
for (let row of rows) {
|
||||
let name = $(".call-tree-name", row).value;
|
||||
switch (name) {
|
||||
case "A":
|
||||
ok($(".opt-icon", row), "found an opt icon on a leaf node with opt data");
|
||||
break;
|
||||
case "C":
|
||||
ok(!$(".opt-icon", row), "frames without opt data do not have an icon");
|
||||
break;
|
||||
case "Gecko":
|
||||
ok(!$(".opt-icon", row), "meta category frames with opt data do not have an icon");
|
||||
break;
|
||||
case "(root)":
|
||||
ok(!$(".opt-icon", row), "root frame certainly does not have opt data");
|
||||
break;
|
||||
default:
|
||||
ok(false, `Unidentified frame: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
|
||||
function *injectAndRenderProfilerData() {
|
||||
// Get current recording and inject our mock data
|
||||
info("Injecting mock profile data");
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
recording._profile = profilerData;
|
||||
|
||||
// Force a rerender
|
||||
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
JsCallTreeView.render(OverviewView.getTimeInterval());
|
||||
yield rendered;
|
||||
}
|
||||
}
|
||||
|
||||
let gUniqueStacks = new RecordingUtils.UniqueStacks();
|
||||
|
||||
function uniqStr(s) {
|
||||
return gUniqueStacks.getOrAddStringIndex(s);
|
||||
}
|
||||
|
||||
// Since deflateThread doesn't handle deflating optimization info, use
|
||||
// placeholder names A_O1, B_O2, and B_O3, which will be used to manually
|
||||
// splice deduped opts into the profile.
|
||||
let gThread = RecordingUtils.deflateThread({
|
||||
samples: [{
|
||||
time: 0,
|
||||
frames: [
|
||||
{ location: "(root)" }
|
||||
]
|
||||
}, {
|
||||
time: 5,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A (http://foo:1)" },
|
||||
]
|
||||
}, {
|
||||
time: 5 + 1,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "C (http://foo/bar/baz:56)" }
|
||||
]
|
||||
}, {
|
||||
time: 5 + 1 + 2,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ category: CATEGORY_MASK("other"), location: "PlatformCode" }
|
||||
]
|
||||
}],
|
||||
markers: []
|
||||
}, gUniqueStacks);
|
||||
|
||||
// 3 RawOptimizationSites
|
||||
let gRawSite1 = {
|
||||
_testFrameInfo: { name: "A", line: "12", file: "@baz" },
|
||||
line: 12,
|
||||
column: 2,
|
||||
types: [{
|
||||
mirType: uniqStr("Object"),
|
||||
site: uniqStr("A (http://foo/bar/bar:12)"),
|
||||
typeset: [{
|
||||
keyedBy: uniqStr("constructor"),
|
||||
name: uniqStr("Foo"),
|
||||
location: uniqStr("A (http://foo/bar/baz:12)")
|
||||
}, {
|
||||
keyedBy: uniqStr("primitive"),
|
||||
location: uniqStr("self-hosted")
|
||||
}]
|
||||
}],
|
||||
attempts: {
|
||||
schema: {
|
||||
outcome: 0,
|
||||
strategy: 1
|
||||
},
|
||||
data: [
|
||||
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
|
||||
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
|
||||
[uniqStr("Failure3"), uniqStr("SomeGetter3")]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
gThread.frameTable.data.forEach((frame) => {
|
||||
const LOCATION_SLOT = gThread.frameTable.schema.location;
|
||||
const OPTIMIZATIONS_SLOT = gThread.frameTable.schema.optimizations;
|
||||
|
||||
let l = gThread.stringTable[frame[LOCATION_SLOT]];
|
||||
switch (l) {
|
||||
case "A (http://foo:1)":
|
||||
frame[LOCATION_SLOT] = uniqStr("A (http://foo:1)");
|
||||
frame[OPTIMIZATIONS_SLOT] = gRawSite1;
|
||||
break;
|
||||
case "PlatformCode":
|
||||
frame[LOCATION_SLOT] = uniqStr("PlatformCode");
|
||||
frame[OPTIMIZATIONS_SLOT] = gRawSite1;
|
||||
break;
|
||||
}
|
||||
});
|
|
@ -11,7 +11,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
rerenderPrefs: [
|
||||
"invert-call-tree",
|
||||
"show-platform-data",
|
||||
"flatten-tree-recursion"
|
||||
"flatten-tree-recursion",
|
||||
],
|
||||
|
||||
rangeChangeDebounceTime: 75, // ms
|
||||
|
@ -42,13 +42,14 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
* The { startTime, endTime }, in milliseconds.
|
||||
*/
|
||||
render: function (interval={}) {
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
let profile = recording.getProfile();
|
||||
let options = {
|
||||
contentOnly: !PerformanceController.getOption("show-platform-data"),
|
||||
invertTree: PerformanceController.getOption("invert-call-tree"),
|
||||
flattenRecursion: PerformanceController.getOption("flatten-tree-recursion")
|
||||
flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
|
||||
showOptimizationHint: recording.getConfiguration().withJITOptimizations,
|
||||
};
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
let profile = recording.getProfile();
|
||||
let threadNode = this._prepareCallTree(profile, interval, options);
|
||||
this._populateCallTree(threadNode, options);
|
||||
this.emit(EVENTS.JS_CALL_TREE_RENDERED);
|
||||
|
@ -104,7 +105,8 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
hidden: inverted,
|
||||
// Call trees should only auto-expand when not inverted. Passing undefined
|
||||
// will default to the CALL_TREE_AUTO_EXPAND depth.
|
||||
autoExpandDepth: inverted ? 0 : undefined
|
||||
autoExpandDepth: inverted ? 0 : undefined,
|
||||
enableOptimizations: options.enableOptimizations
|
||||
});
|
||||
|
||||
// Bind events.
|
||||
|
@ -112,6 +114,9 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
|
||||
// Pipe "focus" events to the view, mostly for tests
|
||||
root.on("focus", () => this.emit("focus"));
|
||||
// TODO tests for optimization event and rendering
|
||||
// optimization bubbles in call tree
|
||||
root.on("optimization", (_, node) => this.emit("optimization", node));
|
||||
|
||||
// Clear out other call trees.
|
||||
this.container.innerHTML = "";
|
||||
|
|
|
@ -111,6 +111,12 @@ table.url.tooltiptext=View source in Debugger
|
|||
# buttons (small magnifying glass icons) which spawn a new tab.
|
||||
table.zoom.tooltiptext=Inspect frame in new tab
|
||||
|
||||
# LOCALIZATION NOTE (table.view-optimizations.tooltiptext):
|
||||
# This string is displayed in the icon displayed next to frames that
|
||||
# have optimization data
|
||||
table.view-optimizations.tooltiptext=View optimizations in JIT View
|
||||
|
||||
|
||||
# LOCALIZATION NOTE (recordingsList.saveDialogTitle):
|
||||
# This string is displayed as a title for saving a recording to disk.
|
||||
recordingsList.saveDialogTitle=Save profile…
|
||||
|
|
|
@ -641,25 +641,38 @@ menuitem.marker-color-graphs-blue:before,
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#jit-optimizations-view .opt-icon::before {
|
||||
.opt-icon::before {
|
||||
content: "";
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 72px 60px;
|
||||
/* show grey "i" bubble by default */
|
||||
background-position: -36px -36px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
display: inline-block;
|
||||
|
||||
margin: 5px 6px 0 0;
|
||||
max-height: 12px;
|
||||
}
|
||||
.theme-light #jit-optimizations-view .opt-icon::before {
|
||||
|
||||
#jit-optimizations-view .opt-icon::before {
|
||||
margin: 5px 6px 0 0;
|
||||
}
|
||||
description.opt-icon {
|
||||
margin: 0px 0px 0px 0px;
|
||||
}
|
||||
description.opt-icon::before {
|
||||
margin: 1px 4px 0px 0px;
|
||||
}
|
||||
.theme-light .opt-icon::before {
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.svg#light-icons);
|
||||
}
|
||||
|
||||
#jit-optimizations-view .opt-icon[severity=warning]::before {
|
||||
.opt-icon[severity=warning]::before {
|
||||
background-position: -24px -24px;
|
||||
}
|
||||
.opt-icon[type=linkable]::before {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul.frames-list {
|
||||
list-style-type: none;
|
||||
|
|
Загрузка…
Ссылка в новой задаче