mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1049825 - Option to invert the call tree. r=pbrosset,vporof
This commit is contained in:
parent
3bd19fb0da
commit
41f1bac0a4
@ -39,6 +39,11 @@
|
||||
</hbox>
|
||||
</toolbar>
|
||||
<vbox id="recordings-list" flex="1"/>
|
||||
<splitter class="devtools-horizontal-splitter" />
|
||||
<vbox id="profile-options" class="devtools-toolbar">
|
||||
<checkbox id="invert-tree" label="&profilerUI.invertTree;"
|
||||
tooltiptext="&profilerUI.invertTree.tooltiptext;" />
|
||||
</vbox>
|
||||
</vbox>
|
||||
|
||||
<deck id="profile-pane"
|
||||
@ -108,14 +113,14 @@
|
||||
type="duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalDuration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfDuration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalPercentage;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfDuration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-percentage"
|
||||
crop="end"
|
||||
|
@ -100,6 +100,7 @@ skip-if = true # Bug 1047124
|
||||
[browser_profiler_tree-model-02.js]
|
||||
[browser_profiler_tree-model-03.js]
|
||||
[browser_profiler_tree-model-04.js]
|
||||
[browser_profiler_tree-model-05.js]
|
||||
[browser_profiler_tree-view-01.js]
|
||||
[browser_profiler_tree-view-02.js]
|
||||
[browser_profiler_tree-view-03.js]
|
||||
|
@ -0,0 +1,79 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if an inverted call tree model can be correctly computed from a samples
|
||||
* array.
|
||||
*/
|
||||
|
||||
let time = 1;
|
||||
|
||||
let samples = [{
|
||||
time: time++,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B" },
|
||||
{ location: "C" }
|
||||
]
|
||||
}, {
|
||||
time: time++,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "D" },
|
||||
{ location: "C" }
|
||||
]
|
||||
}, {
|
||||
time: time++,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "E" },
|
||||
{ location: "C" }
|
||||
],
|
||||
}, {
|
||||
time: time++,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B" },
|
||||
{ location: "F" }
|
||||
]
|
||||
}];
|
||||
|
||||
function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/profiler/tree-model");
|
||||
|
||||
let root = new ThreadNode(samples, undefined, undefined, undefined, true);
|
||||
|
||||
is(Object.keys(root.calls).length, 2,
|
||||
"Should get the 2 youngest frames, not the 1 oldest frame");
|
||||
|
||||
let C = root.calls.C;
|
||||
ok(C, "Should have C as a child of the root.");
|
||||
|
||||
is(Object.keys(C.calls).length, 3,
|
||||
"Should have 3 frames that called C.");
|
||||
ok(C.calls.B, "B called C.");
|
||||
ok(C.calls.D, "D called C.");
|
||||
ok(C.calls.E, "E called C.");
|
||||
|
||||
is(Object.keys(C.calls.B.calls).length, 1);
|
||||
ok(C.calls.B.calls.A, "A called B called C");
|
||||
is(Object.keys(C.calls.D.calls).length, 1);
|
||||
ok(C.calls.D.calls.A, "A called D called C");
|
||||
is(Object.keys(C.calls.E.calls).length, 1);
|
||||
ok(C.calls.E.calls.A, "A called E called C");
|
||||
|
||||
let F = root.calls.F;
|
||||
ok(F, "Should have F as a child of the root.");
|
||||
|
||||
is(Object.keys(F.calls).length, 1);
|
||||
ok(F.calls.B, "B called F");
|
||||
|
||||
is(Object.keys(F.calls.B.calls).length, 1);
|
||||
ok(F.calls.B.calls.A, "A called B called F");
|
||||
|
||||
finish();
|
||||
}
|
@ -32,16 +32,16 @@ function test() {
|
||||
is(container.childNodes[0].childNodes[0].getAttribute("value"), "18",
|
||||
"The root node in the tree has the correct duration cell value.");
|
||||
|
||||
is(container.childNodes[0].childNodes[1].getAttribute("type"), "self-duration",
|
||||
"The root node in the tree has a self-duration cell.");
|
||||
is(container.childNodes[0].childNodes[1].getAttribute("value"), "0",
|
||||
"The root node in the tree has the correct self-duration cell value.");
|
||||
|
||||
is(container.childNodes[0].childNodes[2].getAttribute("type"), "percentage",
|
||||
is(container.childNodes[0].childNodes[1].getAttribute("type"), "percentage",
|
||||
"The root node in the tree has a percentage cell.");
|
||||
is(container.childNodes[0].childNodes[2].getAttribute("value"), "100%",
|
||||
is(container.childNodes[0].childNodes[1].getAttribute("value"), "100%",
|
||||
"The root node in the tree has the correct percentage cell value.");
|
||||
|
||||
is(container.childNodes[0].childNodes[2].getAttribute("type"), "self-duration",
|
||||
"The root node in the tree has a self-duration cell.");
|
||||
is(container.childNodes[0].childNodes[2].getAttribute("value"), "0",
|
||||
"The root node in the tree has the correct self-duration cell value.");
|
||||
|
||||
is(container.childNodes[0].childNodes[3].getAttribute("type"), "self-percentage",
|
||||
"The root node in the tree has a self-percentage cell.");
|
||||
is(container.childNodes[0].childNodes[3].getAttribute("value"), "0%",
|
||||
|
@ -46,10 +46,10 @@ function test() {
|
||||
"The number of columns displayed for tree items is correct.");
|
||||
is(C.target.childNodes[0].getAttribute("type"), "duration",
|
||||
"The first column displayed for tree items is correct.");
|
||||
is(C.target.childNodes[1].getAttribute("type"), "self-duration",
|
||||
"The second column displayed for tree items is correct.");
|
||||
is(C.target.childNodes[2].getAttribute("type"), "percentage",
|
||||
is(C.target.childNodes[1].getAttribute("type"), "percentage",
|
||||
"The third column displayed for tree items is correct.");
|
||||
is(C.target.childNodes[2].getAttribute("type"), "self-duration",
|
||||
"The second column displayed for tree items is correct.");
|
||||
is(C.target.childNodes[3].getAttribute("type"), "self-percentage",
|
||||
"The fourth column displayed for tree items is correct.");
|
||||
is(C.target.childNodes[4].getAttribute("type"), "samples",
|
||||
|
@ -20,6 +20,7 @@ let ProfileView = {
|
||||
this._tabTemplate = $("#profile-content-tab-template");
|
||||
this._panelTemplate = $("#profile-content-tabpanel-template");
|
||||
this._newtabButton = $("#profile-newtab-button");
|
||||
this._invertTree = $("#invert-tree");
|
||||
|
||||
this._recordingInfoByPanel = new WeakMap();
|
||||
this._framerateGraphByPanel = new Map();
|
||||
@ -28,6 +29,7 @@ let ProfileView = {
|
||||
|
||||
this._onTabSelect = this._onTabSelect.bind(this);
|
||||
this._onNewTabClick = this._onNewTabClick.bind(this);
|
||||
this._onInvertTree = this._onInvertTree.bind(this);
|
||||
this._onGraphLegendSelection = this._onGraphLegendSelection.bind(this);
|
||||
this._onGraphMouseUp = this._onGraphMouseUp.bind(this);
|
||||
this._onGraphScroll = this._onGraphScroll.bind(this);
|
||||
@ -37,6 +39,7 @@ let ProfileView = {
|
||||
|
||||
this._panels.addEventListener("select", this._onTabSelect, false);
|
||||
this._newtabButton.addEventListener("click", this._onNewTabClick, false);
|
||||
this._invertTree.addEventListener("command", this._onInvertTree, false);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -47,6 +50,7 @@ let ProfileView = {
|
||||
|
||||
this._panels.removeEventListener("select", this._onTabSelect, false);
|
||||
this._newtabButton.removeEventListener("click", this._onNewTabClick, false);
|
||||
this._invertTree.removeEventListener("command", this._onInvertTree, false);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -301,7 +305,7 @@ let ProfileView = {
|
||||
* Additional options supported by this operation.
|
||||
* @see ProfileView._populatePanelWidgets
|
||||
*/
|
||||
_zoomTreeFromSelection: function(options) {
|
||||
_rebuildTreeFromSelection: function(options) {
|
||||
let { recordingData, displayRange } = this._getRecordingInfo();
|
||||
let categoriesGraph = this._getCategoriesGraph();
|
||||
let selectedPanel = this._getPanel();
|
||||
@ -455,10 +459,16 @@ let ProfileView = {
|
||||
* Additional options supported by this operation.
|
||||
* @see ProfileView._populatePanelWidgets
|
||||
*/
|
||||
_populateCallTree: function(panel, profilerData, beginAt, endAt, options) {
|
||||
_populateCallTree: function(panel, profilerData, beginAt, endAt, options = {}) {
|
||||
let threadSamples = profilerData.profile.threads[0].samples;
|
||||
let contentOnly = !Prefs.showPlatformData;
|
||||
let threadNode = new ThreadNode(threadSamples, contentOnly, beginAt, endAt);
|
||||
let invertChecked = this._invertTree.hasAttribute("checked");
|
||||
let threadNode = new ThreadNode(threadSamples, contentOnly, beginAt, endAt,
|
||||
invertChecked);
|
||||
// If we have an empty profile (no samples), then don't invert the tree, as
|
||||
// it would hide the root node and a completely blank call tree space can be
|
||||
// mis-interpreted as an error.
|
||||
options.inverted = invertChecked && threadNode.samples > 0;
|
||||
this._populateCallTreeFromFrameNode(panel, threadNode, options);
|
||||
},
|
||||
|
||||
@ -480,7 +490,12 @@ let ProfileView = {
|
||||
oldRoot.remove();
|
||||
}
|
||||
|
||||
let callTreeRoot = new CallView({ frame: frameNode });
|
||||
let callTreeRoot = new CallView({
|
||||
autoExpandDepth: options.inverted ? 0 : undefined,
|
||||
frame: frameNode,
|
||||
hidden: options.inverted,
|
||||
inverted: options.inverted
|
||||
});
|
||||
callTreeRoot.on("focus", this._onCallViewFocus);
|
||||
callTreeRoot.on("link", this._onCallViewLink);
|
||||
callTreeRoot.on("zoom", this._onCallViewZoom);
|
||||
@ -544,18 +559,22 @@ let ProfileView = {
|
||||
this._spawnTabFromSelection();
|
||||
},
|
||||
|
||||
_onInvertTree: function() {
|
||||
this._rebuildTreeFromSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the "legend-selection" event for the graphs in this container.
|
||||
*/
|
||||
_onGraphLegendSelection: function() {
|
||||
this._zoomTreeFromSelection({ skipCallTreeFocus: true });
|
||||
this._rebuildTreeFromSelection({ skipCallTreeFocus: true });
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the "mouseup" event for the graphs in this container.
|
||||
*/
|
||||
_onGraphMouseUp: function() {
|
||||
this._zoomTreeFromSelection();
|
||||
this._rebuildTreeFromSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -563,7 +582,7 @@ let ProfileView = {
|
||||
*/
|
||||
_onGraphScroll: function() {
|
||||
setNamedTimeout("graph-scroll", GRAPH_SCROLL_EVENTS_DRAIN, () => {
|
||||
this._zoomTreeFromSelection();
|
||||
this._rebuildTreeFromSelection();
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -50,15 +50,17 @@ exports._isContent = isContent; // used in tests
|
||||
* @see ThreadNode.prototype.insert
|
||||
* @param number endAt [optional]
|
||||
* @see ThreadNode.prototype.insert
|
||||
* @param boolean invert [optional]
|
||||
* @see ThreadNode.prototype.insert
|
||||
*/
|
||||
function ThreadNode(threadSamples, contentOnly, beginAt, endAt) {
|
||||
function ThreadNode(threadSamples, contentOnly, beginAt, endAt, invert) {
|
||||
this.samples = 0;
|
||||
this.duration = 0;
|
||||
this.calls = {};
|
||||
this._previousSampleTime = 0;
|
||||
|
||||
for (let sample of threadSamples) {
|
||||
this.insert(sample, contentOnly, beginAt, endAt);
|
||||
this.insert(sample, contentOnly, beginAt, endAt, invert);
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,33 +78,47 @@ ThreadNode.prototype = {
|
||||
* The earliest sample to start at (in milliseconds).
|
||||
* @param number endAt [optional]
|
||||
* The latest sample to end at (in milliseconds).
|
||||
* @param boolean inverted [optional]
|
||||
* Specifies if the call tree should be inverted (youngest -> oldest
|
||||
* frames).
|
||||
*/
|
||||
insert: function(sample, contentOnly = false, beginAt = 0, endAt = Infinity) {
|
||||
insert: function(sample, contentOnly = false, beginAt = 0, endAt = Infinity,
|
||||
inverted = false) {
|
||||
let sampleTime = sample.time;
|
||||
if (!sampleTime || sampleTime < beginAt || sampleTime > endAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sampleFrames = sample.frames;
|
||||
let rootIndex = 1;
|
||||
|
||||
// Filter out platform frames if only content-related function calls
|
||||
// should be taken into consideration.
|
||||
if (contentOnly) {
|
||||
sampleFrames = sampleFrames.filter(frame => isContent(frame));
|
||||
rootIndex = 0;
|
||||
sampleFrames = sampleFrames.filter(isContent);
|
||||
}
|
||||
|
||||
if (!sampleFrames.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (inverted) {
|
||||
sampleFrames.reverse();
|
||||
if (!contentOnly) {
|
||||
// Remove the (root) node -- we don't want it as a leaf in the inverted
|
||||
// tree.
|
||||
sampleFrames.pop();
|
||||
}
|
||||
}
|
||||
|
||||
let startIndex = (inverted || contentOnly) ? 0 : 1;
|
||||
|
||||
let sampleDuration = sampleTime - this._previousSampleTime;
|
||||
this._previousSampleTime = sampleTime;
|
||||
this.samples++;
|
||||
this.duration += sampleDuration;
|
||||
|
||||
FrameNode.prototype.insert(
|
||||
sampleFrames, rootIndex, sampleTime, sampleDuration, this.calls);
|
||||
sampleFrames, startIndex, sampleTime, sampleDuration, this.calls);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -37,6 +37,10 @@ exports.CallView = CallView;
|
||||
* Every instance of a `CallView` represents a row in the call tree. The same
|
||||
* parent node is used for all rows.
|
||||
*
|
||||
* @param number autoExpandDepth [optional]
|
||||
* The depth to which the tree should automatically expand. Defualts to
|
||||
* the caller's autoExpandDepth if a caller exists, otherwise defaults to
|
||||
* CALL_TREE_AUTO_EXPAND.
|
||||
* @param CallView caller
|
||||
* The CallView considered the "caller" frame. This instance will be
|
||||
* represent the "callee". Should be null for root nodes.
|
||||
@ -44,12 +48,31 @@ exports.CallView = CallView;
|
||||
* Details about this function, like { samples, duration, calls } etc.
|
||||
* @param number level
|
||||
* The indentation level in the call tree. The root node is at level 0.
|
||||
* @param boolean hidden [optional]
|
||||
* Whether this node should be hidden and not contribute to depth/level
|
||||
* calculations. Defaults to false.
|
||||
* @param boolean inverted [optional]
|
||||
* Whether the call tree has been inverted (bottom up, rather than
|
||||
* top-down). Defaults to false.
|
||||
*/
|
||||
function CallView({ caller, frame, level }) {
|
||||
AbstractTreeItem.call(this, { parent: caller, level: level });
|
||||
function CallView({ autoExpandDepth, caller, frame, level, hidden, inverted }) {
|
||||
level = level || 0;
|
||||
if (hidden) {
|
||||
level--;
|
||||
}
|
||||
|
||||
this.autoExpandDepth = caller ? caller.autoExpandDepth : CALL_TREE_AUTO_EXPAND;
|
||||
AbstractTreeItem.call(this, {
|
||||
parent: caller,
|
||||
level
|
||||
});
|
||||
|
||||
this.caller = caller;
|
||||
this.autoExpandDepth = autoExpandDepth != null
|
||||
? autoExpandDepth
|
||||
: caller ? caller.autoExpandDepth : CALL_TREE_AUTO_EXPAND;
|
||||
this.frame = frame;
|
||||
this.hidden = hidden;
|
||||
this.inverted = inverted;
|
||||
|
||||
this._onUrlClick = this._onUrlClick.bind(this);
|
||||
this._onZoomClick = this._onZoomClick.bind(this);
|
||||
@ -67,11 +90,26 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
|
||||
let frameInfo = this.frame.getInfo();
|
||||
let framePercentage = this._getPercentage(this.frame.samples);
|
||||
let childrenPercentage = sum([this._getPercentage(c.samples)
|
||||
|
||||
let selfPercentage;
|
||||
let selfDuration;
|
||||
if (!this._getChildCalls().length) {
|
||||
selfPercentage = framePercentage;
|
||||
selfDuration = this.frame.duration;
|
||||
} else {
|
||||
let childrenPercentage = sum([this._getPercentage(c.samples)
|
||||
for (c of this._getChildCalls())]);
|
||||
selfPercentage = clamp(framePercentage - childrenPercentage, 0, 100);
|
||||
|
||||
let childrenDuration = sum([c.duration
|
||||
for (c of this._getChildCalls())]);
|
||||
let selfPercentage = clamp(framePercentage - childrenPercentage, 0, 100);
|
||||
let selfDuration = this.frame.duration - sum([c.duration
|
||||
for (c of this._getChildCalls())]);
|
||||
selfDuration = this.frame.duration - childrenDuration;
|
||||
|
||||
if (this.inverted) {
|
||||
selfPercentage = framePercentage - selfPercentage;
|
||||
selfDuration = this.frame.duration - selfDuration;
|
||||
}
|
||||
}
|
||||
|
||||
let durationCell = this._createTimeCell(this.frame.duration);
|
||||
let selfDurationCell = this._createTimeCell(selfDuration, true);
|
||||
@ -85,6 +123,9 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
targetNode.setAttribute("origin", frameInfo.isContent ? "content" : "chrome");
|
||||
targetNode.setAttribute("category", frameInfo.categoryData.abbrev || "");
|
||||
targetNode.setAttribute("tooltiptext", this.frame.location || "");
|
||||
if (this.hidden) {
|
||||
targetNode.style.display = "none";
|
||||
}
|
||||
|
||||
let isRoot = frameInfo.nodeType == "Thread";
|
||||
if (isRoot) {
|
||||
@ -93,8 +134,8 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
}
|
||||
|
||||
targetNode.appendChild(durationCell);
|
||||
targetNode.appendChild(selfDurationCell);
|
||||
targetNode.appendChild(percentageCell);
|
||||
targetNode.appendChild(selfDurationCell);
|
||||
targetNode.appendChild(selfPercentageCell);
|
||||
targetNode.appendChild(samplesCell);
|
||||
targetNode.appendChild(functionCell);
|
||||
@ -105,14 +146,14 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
/**
|
||||
* Calculate what percentage of all samples the given number of samples is.
|
||||
*/
|
||||
_getPercentage: function (samples) {
|
||||
_getPercentage: function(samples) {
|
||||
return samples / this.root.frame.samples * 100;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an array of this frame's child calls.
|
||||
*/
|
||||
_getChildCalls: function () {
|
||||
_getChildCalls: function() {
|
||||
return Object.keys(this.frame.calls).map(k => this.frame.calls[k]);
|
||||
},
|
||||
|
||||
@ -128,7 +169,8 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
children.push(new CallView({
|
||||
caller: this,
|
||||
frame: newFrame,
|
||||
level: newLevel
|
||||
level: newLevel,
|
||||
inverted: this.inverted
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,10 @@
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/event-emitter.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["AbstractTreeItem"];
|
||||
|
||||
|
@ -37,6 +37,14 @@
|
||||
- on a button that remvoes all the recordings. -->
|
||||
<!ENTITY profilerUI.clearButton "Clear">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.invertTree): This is the label shown next to
|
||||
- a checkbox that inverts and un-inverts the profiler's call tree. -->
|
||||
<!ENTITY profilerUI.invertTree "Invert Call Tree">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.invertTree.tooltiptext): This is the tooltip
|
||||
- for the tree-inverting checkbox's label. -->
|
||||
<!ENTITY profilerUI.invertTree.tooltiptext "Inverting the call tree displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.table.*): These strings are displayed
|
||||
- in the call tree headers for a recording. -->
|
||||
<!ENTITY profilerUI.table.totalDuration "Total Time (ms)">
|
||||
|
@ -50,12 +50,9 @@
|
||||
|
||||
/* Recordings pane */
|
||||
|
||||
#recordings-pane > tabs {
|
||||
-moz-border-end: 1px solid;
|
||||
}
|
||||
|
||||
#recordings-pane > tabs,
|
||||
#recordings-pane .devtools-toolbar {
|
||||
-moz-border-end: 1px solid;
|
||||
-moz-border-end-width: 1px;
|
||||
}
|
||||
|
||||
.theme-dark #recordings-pane > tabs,
|
||||
|
Loading…
Reference in New Issue
Block a user