mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
b9843e0358
- Bug 1167459 - Skip rendering function name nodes if there's no name available (e.g. for C++ pseudoframes), r=jsantell (4a69ed224)
- missing bits Bug 1102219 - Part 4: Replace String.prototype.contains with `String.prototype.includes` in chrome code. r=till (73cd2d2b1)
- Bug 1165045 - Don't create nodes with empty text in the call tree, r=jsantell (b013aa82d)
- Bug 1166122 - Fix regression in the call tree caused by bug 1165045, r=jsantell (ee3f16901)
- Bug 1167975 - CallView._displaySelf sets this.document just because other functions use it; it should pass it as an argument instead, r=jsantell (5ef560c4f)
- Bug 1122662 - Resize graphs when window resizes;r=vporof (25c108e4e)
- Bug 1164784 - Eliminate CSS duplication with perf tool record button r=jsantell (43c9bb999)
- Bug 1150761 - Rename the performance tool's details view names to better describe the data visualizations. r=vp (04ceb6a37)
- Bug 1144424 - Rename '{self,total} allocations' to '{self,total} sampled allocations' in the performance tool. r=jsantell (ae79ad54f)
- Bug 1069910 - Add tooltips explaining what each column in the profiler's tree view represents; r=jsantell (8756f88b6)
- Bug 1107849 - Define a min/max width for the performance panel sidebar. r=vporof (e1769e831)
- Bug 11663354 - A locked recording button should appear disabled in the performance tool. r=vp (4a359d39e)
- Bug 1023546 - DevTools - Support HDPI resolutions for Windows. r=bgrins (ef1a3ecb8)
- Bug 1168125 - Cleanup performance xul and css, r=jsantell (8ec794e46)
- Bug 1168125 - Replace the waterfall view with a tree, r=jsantell (ea76514fe)
- Bug 1168125 - Add marker folding logic, r=jsantell (1d3748d2a)
- Bug 862341 Part 1: Move the network request storage from the console frontend to the console client so the netmonitor can reuse it. r=vporof (d29fb2b73)
- remove gre from resource path (126b00df1)
- Bug 943306 - Allow persisting console input history between sessions;r=past (146ebb486)
- Bug 1134845 - Add clearHistory jsterm helper to remove persisted console input history. r=past (22237e95b)
- Bug 1143497 - Offer a way to extend WebConsole commands. r=bgrins (84e2d2957)
- Bug 1125205 - Display console API messages from shared or service workers to the web console, r=past (b4b701a2c)
- Bug 1169342 - Remove nsIDOMDeviceStorage. Cleanup nsDOMDeviceStorage event wrappers. r=dhylands (41338e16f)
- Bug 1151610 - Manage the case where two extensions fight over the same command. r=bgrins (63f9d2064)
- Bug 862341 Part 2: Display cached network requests in the web console. r=vporof (83c0e7263)
- Bug 1144211 - Improve code coverage of camera mochitests. r=mikeh (ba9f3de89)
- Bug 1152500 - Fix how stop recording may be handled out-of-order. r=dhylands (d8bdd379c)
- Bug 862341 Part 3: Display cached network requests in the network panel. r=vporof (a1a6f151d)
- Bug 862341 Part 4: Start recording network requests when the toolbox opens. r=vporof (7a2bdf847)
- Bug 1151499 - Correct the FM playable state. r=baku (8af26fff2)
- Bug 1180347 - Split media.useAudioChannelService to support turning the service on without turning the Firefox OS specific APIs on; r=baku (3fa29291a)
- Bug 862341 Part 5: Tests. r=vporof (82fb944c6)
403 lines
14 KiB
JavaScript
403 lines
14 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 tree view, displaying all the samples and frames
|
|
* received from the proviler in a tree-like structure.
|
|
*/
|
|
|
|
const { Cc, Ci, Cu, Cr } = require("chrome");
|
|
const { L10N } = require("devtools/performance/global");
|
|
const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
|
const { AbstractTreeItem } = require("resource:///modules/devtools/AbstractTreeItem.jsm");
|
|
|
|
const MILLISECOND_UNITS = L10N.getStr("table.ms");
|
|
const PERCENTAGE_UNITS = L10N.getStr("table.percentage");
|
|
const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
|
|
const CALL_TREE_INDENTATION = 16; // px
|
|
|
|
const DEFAULT_SORTING_PREDICATE = (frameA, frameB) => {
|
|
let dataA = frameA.getDisplayedData();
|
|
let dataB = frameB.getDisplayedData();
|
|
return this.inverted
|
|
? (dataA.selfPercentage < dataB.selfPercentage ? 1 : -1)
|
|
: (dataA.samples < dataB.samples ? 1 : -1);
|
|
};
|
|
|
|
const DEFAULT_AUTO_EXPAND_DEPTH = 3; // depth
|
|
const DEFAULT_VISIBLE_CELLS = {
|
|
duration: true,
|
|
percentage: true,
|
|
allocations: false,
|
|
selfDuration: true,
|
|
selfPercentage: true,
|
|
selfAllocations: false,
|
|
samples: true,
|
|
function: true
|
|
};
|
|
|
|
const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
|
|
const sum = vals => vals.reduce((a, b) => a + b, 0);
|
|
|
|
/**
|
|
* An item in a call tree view, which looks like this:
|
|
*
|
|
* Time (ms) | Cost | Calls | Function
|
|
* ============================================================================
|
|
* 1,000.00 | 100.00% | | ▼ (root)
|
|
* 500.12 | 50.01% | 300 | ▼ foo Categ. 1
|
|
* 300.34 | 30.03% | 1500 | ▼ bar Categ. 2
|
|
* 10.56 | 0.01% | 42 | ▶ call_with_children Categ. 3
|
|
* 90.78 | 0.09% | 25 | call_without_children Categ. 4
|
|
*
|
|
* Every instance of a `CallView` represents a row in the call tree. The same
|
|
* parent node is used for all rows.
|
|
*
|
|
* @param CallView caller
|
|
* The CallView considered the "caller" frame. This newly created
|
|
* instance will be represent the "callee". Should be null for root nodes.
|
|
* @param ThreadNode | FrameNode frame
|
|
* Details about this function, like { samples, duration, calls } etc.
|
|
* @param number level [optional]
|
|
* 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.
|
|
* @param function sortingPredicate [optional]
|
|
* The predicate used to sort the tree items when created. Defaults to
|
|
* the caller's `sortingPredicate` if a caller exists, otherwise defaults
|
|
* to DEFAULT_SORTING_PREDICATE. The two passed arguments are FrameNodes.
|
|
* @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 DEFAULT_AUTO_EXPAND_DEPTH.
|
|
* @param object visibleCells
|
|
* 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.
|
|
*/
|
|
function CallView({
|
|
caller, frame, level, hidden, inverted,
|
|
sortingPredicate, autoExpandDepth, visibleCells
|
|
}) {
|
|
AbstractTreeItem.call(this, {
|
|
parent: caller,
|
|
level: level|0 - (hidden ? 1 : 0)
|
|
});
|
|
|
|
this.sortingPredicate = sortingPredicate != null
|
|
? sortingPredicate
|
|
: caller ? caller.sortingPredicate
|
|
: DEFAULT_SORTING_PREDICATE
|
|
|
|
this.autoExpandDepth = autoExpandDepth != null
|
|
? autoExpandDepth
|
|
: caller ? caller.autoExpandDepth
|
|
: DEFAULT_AUTO_EXPAND_DEPTH;
|
|
|
|
this.visibleCells = visibleCells != null
|
|
? visibleCells
|
|
: caller ? caller.visibleCells
|
|
: Object.create(DEFAULT_VISIBLE_CELLS);
|
|
|
|
this.caller = caller;
|
|
this.frame = frame;
|
|
this.hidden = hidden;
|
|
this.inverted = inverted;
|
|
|
|
this._onUrlClick = this._onUrlClick.bind(this);
|
|
};
|
|
|
|
CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
|
/**
|
|
* Creates the view for this tree node.
|
|
* @param nsIDOMNode document
|
|
* @param nsIDOMNode arrowNode
|
|
* @return nsIDOMNode
|
|
*/
|
|
_displaySelf: function(document, arrowNode) {
|
|
let displayedData = this.getDisplayedData();
|
|
let frameInfo = this.frame.getInfo();
|
|
|
|
if (this.visibleCells.duration) {
|
|
var durationCell = this._createTimeCell(document, displayedData.totalDuration);
|
|
}
|
|
if (this.visibleCells.selfDuration) {
|
|
var selfDurationCell = this._createTimeCell(document, displayedData.selfDuration, true);
|
|
}
|
|
if (this.visibleCells.percentage) {
|
|
var percentageCell = this._createExecutionCell(document, displayedData.totalPercentage);
|
|
}
|
|
if (this.visibleCells.selfPercentage) {
|
|
var selfPercentageCell = this._createExecutionCell(document, displayedData.selfPercentage, true);
|
|
}
|
|
if (this.visibleCells.allocations) {
|
|
var allocationsCell = this._createAllocationsCell(document, displayedData.totalAllocations);
|
|
}
|
|
if (this.visibleCells.selfAllocations) {
|
|
var selfAllocationsCell = this._createAllocationsCell(document, displayedData.selfAllocations, true);
|
|
}
|
|
if (this.visibleCells.samples) {
|
|
var samplesCell = this._createSamplesCell(document, displayedData.samples);
|
|
}
|
|
if (this.visibleCells.function) {
|
|
var functionCell = this._createFunctionCell(document, arrowNode, displayedData.name, frameInfo, this.level);
|
|
}
|
|
|
|
let targetNode = document.createElement("hbox");
|
|
targetNode.className = "call-tree-item";
|
|
targetNode.setAttribute("origin", frameInfo.isContent ? "content" : "chrome");
|
|
targetNode.setAttribute("category", frameInfo.categoryData.abbrev || "");
|
|
targetNode.setAttribute("tooltiptext", displayedData.tooltiptext);
|
|
|
|
if (this.hidden) {
|
|
targetNode.style.display = "none";
|
|
}
|
|
if (this.visibleCells.duration) {
|
|
targetNode.appendChild(durationCell);
|
|
}
|
|
if (this.visibleCells.percentage) {
|
|
targetNode.appendChild(percentageCell);
|
|
}
|
|
if (this.visibleCells.allocations) {
|
|
targetNode.appendChild(allocationsCell);
|
|
}
|
|
if (this.visibleCells.selfDuration) {
|
|
targetNode.appendChild(selfDurationCell);
|
|
}
|
|
if (this.visibleCells.selfPercentage) {
|
|
targetNode.appendChild(selfPercentageCell);
|
|
}
|
|
if (this.visibleCells.selfAllocations) {
|
|
targetNode.appendChild(selfAllocationsCell);
|
|
}
|
|
if (this.visibleCells.samples) {
|
|
targetNode.appendChild(samplesCell);
|
|
}
|
|
if (this.visibleCells.function) {
|
|
targetNode.appendChild(functionCell);
|
|
}
|
|
|
|
return targetNode;
|
|
},
|
|
|
|
/**
|
|
* Populates this node in the call tree with the corresponding "callees".
|
|
* These are defined in the `frame` data source for this call view.
|
|
* @param array:AbstractTreeItem children
|
|
*/
|
|
_populateSelf: function(children) {
|
|
let newLevel = this.level + 1;
|
|
|
|
for (let newFrame of this.frame.calls) {
|
|
children.push(new CallView({
|
|
caller: this,
|
|
frame: newFrame,
|
|
level: newLevel,
|
|
inverted: this.inverted
|
|
}));
|
|
}
|
|
|
|
// Sort the "callees" asc. by samples, before inserting them in the tree,
|
|
// if no other sorting predicate was specified on this on the root item.
|
|
children.sort(this.sortingPredicate.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Functions creating each cell in this call view.
|
|
* Invoked by `_displaySelf`.
|
|
*/
|
|
_createTimeCell: function(doc, duration, isSelf = false) {
|
|
let cell = doc.createElement("description");
|
|
cell.className = "plain call-tree-cell";
|
|
cell.setAttribute("type", isSelf ? "self-duration" : "duration");
|
|
cell.setAttribute("crop", "end");
|
|
cell.setAttribute("value", L10N.numberWithDecimals(duration, 2) + " " + MILLISECOND_UNITS);
|
|
return cell;
|
|
},
|
|
_createExecutionCell: function(doc, percentage, isSelf = false) {
|
|
let cell = doc.createElement("description");
|
|
cell.className = "plain call-tree-cell";
|
|
cell.setAttribute("type", isSelf ? "self-percentage" : "percentage");
|
|
cell.setAttribute("crop", "end");
|
|
cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + PERCENTAGE_UNITS);
|
|
return cell;
|
|
},
|
|
_createAllocationsCell: function(doc, count, isSelf = false) {
|
|
let cell = doc.createElement("description");
|
|
cell.className = "plain call-tree-cell";
|
|
cell.setAttribute("type", isSelf ? "self-allocations" : "allocations");
|
|
cell.setAttribute("crop", "end");
|
|
cell.setAttribute("value", count || 0);
|
|
return cell;
|
|
},
|
|
_createSamplesCell: function(doc, count) {
|
|
let cell = doc.createElement("description");
|
|
cell.className = "plain call-tree-cell";
|
|
cell.setAttribute("type", "samples");
|
|
cell.setAttribute("crop", "end");
|
|
cell.setAttribute("value", count || "");
|
|
return cell;
|
|
},
|
|
_createFunctionCell: function(doc, arrowNode, frameName, frameInfo, frameLevel) {
|
|
let cell = doc.createElement("hbox");
|
|
cell.className = "call-tree-cell";
|
|
cell.style.MozMarginStart = (frameLevel * CALL_TREE_INDENTATION) + "px";
|
|
cell.setAttribute("type", "function");
|
|
cell.appendChild(arrowNode);
|
|
|
|
// Don't render a name label node if there's no function name. A different
|
|
// location label node will be rendered instead.
|
|
if (frameName) {
|
|
let nameNode = doc.createElement("description");
|
|
nameNode.className = "plain call-tree-name";
|
|
nameNode.setAttribute("flex", "1");
|
|
nameNode.setAttribute("crop", "end");
|
|
nameNode.setAttribute("value", frameName);
|
|
cell.appendChild(nameNode);
|
|
}
|
|
|
|
// Don't render detailed labels for meta category frames
|
|
if (!frameInfo.isMetaCategory) {
|
|
this._appendFunctionDetailsCells(doc, cell, frameInfo);
|
|
}
|
|
|
|
// Don't render an expando-arrow for leaf nodes.
|
|
let hasDescendants = Object.keys(this.frame.calls).length > 0;
|
|
if (!hasDescendants) {
|
|
arrowNode.setAttribute("invisible", "");
|
|
}
|
|
|
|
return cell;
|
|
},
|
|
_appendFunctionDetailsCells: function(doc, cell, frameInfo) {
|
|
if (frameInfo.fileName) {
|
|
let urlNode = doc.createElement("description");
|
|
urlNode.className = "plain call-tree-url";
|
|
urlNode.setAttribute("flex", "1");
|
|
urlNode.setAttribute("crop", "end");
|
|
urlNode.setAttribute("value", frameInfo.fileName);
|
|
urlNode.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + frameInfo.url);
|
|
urlNode.addEventListener("mousedown", this._onUrlClick);
|
|
cell.appendChild(urlNode);
|
|
}
|
|
|
|
if (frameInfo.line) {
|
|
let lineNode = doc.createElement("description");
|
|
lineNode.className = "plain call-tree-line";
|
|
lineNode.setAttribute("value", ":" + frameInfo.line);
|
|
cell.appendChild(lineNode);
|
|
}
|
|
|
|
if (frameInfo.column) {
|
|
let columnNode = doc.createElement("description");
|
|
columnNode.className = "plain call-tree-column";
|
|
columnNode.setAttribute("value", ":" + frameInfo.column);
|
|
cell.appendChild(columnNode);
|
|
}
|
|
|
|
if (frameInfo.host) {
|
|
let hostNode = doc.createElement("description");
|
|
hostNode.className = "plain call-tree-host";
|
|
hostNode.setAttribute("value", frameInfo.host);
|
|
cell.appendChild(hostNode);
|
|
}
|
|
|
|
let spacerNode = doc.createElement("spacer");
|
|
spacerNode.setAttribute("flex", "10000");
|
|
cell.appendChild(spacerNode);
|
|
|
|
if (frameInfo.categoryData.label) {
|
|
let categoryNode = doc.createElement("description");
|
|
categoryNode.className = "plain call-tree-category";
|
|
categoryNode.style.color = frameInfo.categoryData.color;
|
|
categoryNode.setAttribute("value", frameInfo.categoryData.label);
|
|
cell.appendChild(categoryNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Gets the data displayed about this tree item, based on the FrameNode
|
|
* model associated with this view.
|
|
*
|
|
* @return object
|
|
*/
|
|
getDisplayedData: function() {
|
|
if (this._cachedDisplayedData) {
|
|
return this._cachedDisplayedData;
|
|
}
|
|
|
|
let data = this._cachedDisplayedData = Object.create(null);
|
|
let frameInfo = this.frame.getInfo();
|
|
|
|
// Self/total duration.
|
|
if (this.visibleCells.duration) {
|
|
data.totalDuration = this.frame.duration;
|
|
}
|
|
if (this.visibleCells.selfDuration) {
|
|
data.selfDuration = this.root.frame.selfDuration[this.frame.key];
|
|
}
|
|
|
|
// Self/total samples percentage.
|
|
if (this.visibleCells.percentage) {
|
|
data.totalPercentage = this.frame.samples / this.root.frame.samples * 100;
|
|
}
|
|
if (this.visibleCells.selfPercentage) {
|
|
data.selfPercentage = this.root.frame.selfCount[this.frame.key] / this.root.frame.samples * 100;
|
|
}
|
|
|
|
// Self/total allocations count.
|
|
if (this.visibleCells.allocations) {
|
|
let childrenAllocations = this.frame.calls.reduce((acc, node) => acc + node.allocations, 0);
|
|
data.totalAllocations = this.frame.allocations + childrenAllocations;
|
|
}
|
|
if (this.visibleCells.selfAllocations) {
|
|
data.selfAllocations = this.frame.allocations;
|
|
}
|
|
|
|
// Raw samples.
|
|
if (this.visibleCells.samples) {
|
|
data.samples = this.frame.samples;
|
|
}
|
|
|
|
// Frame name (function location or some meta information).
|
|
data.name = frameInfo.isMetaCategory
|
|
? frameInfo.categoryData.label
|
|
: frameInfo.functionName || "";
|
|
|
|
data.tooltiptext = frameInfo.isMetaCategory
|
|
? frameInfo.categoryData.label
|
|
: this.frame.location || "";
|
|
|
|
return this._cachedDisplayedData;
|
|
},
|
|
|
|
/**
|
|
* Toggles the category information hidden or visible.
|
|
* @param boolean visible
|
|
*/
|
|
toggleCategories: function(visible) {
|
|
if (!visible) {
|
|
this.container.setAttribute("categories-hidden", "");
|
|
} else {
|
|
this.container.removeAttribute("categories-hidden");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handler for the "click" event on the url node of this call view.
|
|
*/
|
|
_onUrlClick: function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.root.emit("link", this);
|
|
}
|
|
});
|
|
|
|
exports.CallView = CallView;
|