tenfourfox/devtools/client/performance/views/optimizations-list.js

397 lines
12 KiB
JavaScript
Raw Normal View History

2017-04-19 07:56:45 +00:00
/* 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/. */
/* import-globals-from ../performance-controller.js */
/* import-globals-from ../performance-view.js */
/* globals document */
"use strict";
const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
const OPTIMIZATION_FAILURE = L10N.getStr("jit.optimizationFailure");
const JIT_SAMPLES = L10N.getStr("jit.samples");
const JIT_EMPTY_TEXT = L10N.getStr("jit.empty");
const PROPNAME_MAX_LENGTH = 4;
/**
* View for rendering a list of all optmizations found in a frame.
* The terminology and types used here can be referenced:
* @see devtools/client/performance/modules/logic/jit.js
*/
var OptimizationsListView = {
_currentFrame: null,
/**
* Initialization function called when the tool starts up.
*/
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");
this.$headerFile = $("#jit-optimizations-header .header-file");
this.$headerLine = $("#jit-optimizations-header .header-line");
this.tree = new TreeWidget($("#jit-optimizations-raw-view"), {
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;
},
/**
* Takes a FrameNode, with corresponding optimization data to be displayed
* in the view.
*
* @param {FrameNode} frameNode
*/
setCurrentFrame: function (threadNode, frameNode) {
if (threadNode !== this.getCurrentThread()) {
this._currentThread = threadNode;
}
if (frameNode !== this.getCurrentFrame()) {
this._currentFrame = frameNode;
}
},
/**
* Returns the current frame node for this view.
*
* @return {?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, null);
this.clear();
this.el.classList.add("empty");
this.emit(EVENTS.OPTIMIZATIONS_RESET);
this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame());
},
/**
* Clears out data in the tree.
*/
clear: function () {
this.tree.clear();
},
/**
* Takes a JITOptimizations object and builds a view containing all attempted
* optimizations for this frame. This view is very verbose and meant for those
* who understand JIT compilers.
*/
render: function () {
let frameNode = this.getCurrentFrame();
if (!frameNode) {
this.reset();
return;
}
let view = this.tree;
// Set header information, even if the frame node
// does not have any optimization data
let frameData = frameNode.getInfo();
this._setHeaders(frameData);
this.clear();
// If this frame node does not have optimizations, or if its a meta node in the
// case of only showing content, reset the view.
if (!frameNode.hasOptimizations() || frameNode.isMetaCategory) {
this.reset();
return;
}
this.el.classList.remove("empty");
// An array of sorted OptimizationSites.
let sites = frameNode.getOptimizations().optimizationSites;
for (let site of sites) {
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.
*/
_renderSite: function (view, site, frameData) {
let { id, samples, data } = site;
let { types, attempts } = data;
let siteNode = this._createSiteNode(frameData, site);
// Cast `id` to a string so TreeWidget doesn't think it does not exist
id = id + "";
view.add([{ id: id, node: siteNode }]);
// Add types -- Ion types are the parent, with
// the observed types as children.
view.add([id, { id: `${id}-types`, label: `Types (${types.length})` }]);
this._renderIonType(view, site);
// Add attempts
view.add([id, { id: `${id}-attempts`, label: `Attempts (${attempts.length})` }]);
for (let i = attempts.length - 1; i >= 0; i--) {
let node = this._createAttemptNode(attempts[i]);
view.add([id, `${id}-attempts`, { node }]);
}
},
/**
* Renders all Ion types from an optimization site, with its children
* ObservedTypes.
*/
_renderIonType: function (view, site) {
let { id, data: { types }} = site;
// Cast `id` to a string so TreeWidget doesn't think it does not exist
id = id + "";
for (let i = 0; i < types.length; i++) {
let ionType = types[i];
let ionNode = this._createIonNode(ionType);
view.add([id, `${id}-types`, { id: `${id}-types-${i}`, node: ionNode }]);
for (let observedType of (ionType.typeset || [])) {
let node = this._createObservedTypeNode(observedType);
view.add([id, `${id}-types`, `${id}-types-${i}`, { node }]);
}
}
},
/**
* 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");
let line = document.createElement("span");
let column = document.createElement("span");
let urlNode = this._createDebuggerLinkNode(frameData.url, site.data.line);
let attempts = site.getAttempts();
let lastStrategy = attempts[attempts.length - 1].strategy;
let propString = "";
if (site.data.propertyName) {
if (site.data.propertyName.length > PROPNAME_MAX_LENGTH) {
propString = ` (.${site.data.propertyName.substr(0, PROPNAME_MAX_LENGTH)}…)`;
desc.setAttribute("tooltiptext", site.data.propertyName);
} else {
propString = ` (.${site.data.propertyName})`;
}
}
if (!site.hasSuccessfulOutcome()) {
let icon = document.createElement("span");
icon.setAttribute("tooltiptext", OPTIMIZATION_FAILURE);
icon.setAttribute("severity", "warning");
icon.className = "opt-icon";
node.appendChild(icon);
}
let sampleString = PluralForm.get(site.samples, JIT_SAMPLES).replace("#1", site.samples);
desc.textContent = `${lastStrategy}${propString} (${sampleString})`;
line.textContent = site.data.line;
line.className = "opt-line";
column.textContent = site.data.column;
column.className = "opt-line";
node.appendChild(desc);
node.appendChild(urlNode);
node.appendChild(line);
node.appendChild(column);
return node;
},
/**
* Creates an element for insertion in the raw view for an IonType.
*
* @see devtools/client/performance/modules/logic/jit.js
* @param {IonType} ionType
* @return {Element}
*/
_createIonNode: function (ionType) {
let node = document.createElement("span");
node.textContent = `${ionType.site} : ${ionType.mirType}`;
node.className = "opt-ion-type";
return node;
},
/**
* Creates an element for insertion in the raw view for an ObservedType.
*
* @see devtools/client/performance/modules/logic/jit.js
* @param {ObservedType} type
* @return {Element}
*/
_createObservedTypeNode: function (type) {
let node = document.createElement("span");
let typeNode = document.createElement("span");
typeNode.textContent = `${type.keyedBy}` + (type.name ? `${type.name}` : "");
typeNode.className = "opt-type";
node.appendChild(typeNode);
// If we have a type and a location, try to make a
// link to the debugger
if (type.location && type.line) {
let urlNode = this._createDebuggerLinkNode(type.location, type.line);
node.appendChild(urlNode);
}
// Otherwise if we just have a location, it could just
// be a memory location
else if (type.location) {
let locNode = document.createElement("span");
locNode.textContent = `@${type.location}`;
locNode.className = "opt-url";
node.appendChild(locNode);
}
if (type.line) {
let line = document.createElement("span");
line.textContent = type.line;
line.className = "opt-line";
node.appendChild(line);
}
return node;
},
/**
* Creates an element for insertion in the raw view for an OptimizationAttempt.
*
* @see devtools/client/performance/modules/logic/jit.js
* @param {OptimizationAttempt} attempt
* @return {Element}
*/
_createAttemptNode: function (attempt) {
let node = document.createElement("span");
let strategyNode = document.createElement("span");
let outcomeNode = document.createElement("span");
strategyNode.textContent = attempt.strategy;
strategyNode.className = "opt-strategy";
outcomeNode.textContent = attempt.outcome;
outcomeNode.className = "opt-outcome";
outcomeNode.setAttribute("outcome",
JITOptimizations.isSuccessfulOutcome(attempt.outcome) ? "success" : "failure");
node.appendChild(strategyNode);
node.appendChild(outcomeNode);
node.className = "opt-attempt";
return node;
},
/**
* Creates a new element, linking it up to the debugger upon clicking.
* Can also optionally pass in an element to modify it rather than
* creating a new one.
*
* @param {String} url
* @param {Number} line
* @param {?Element} el
* @return {Element}
*/
_createDebuggerLinkNode: function (url, line, el) {
let node = el || document.createElement("span");
node.className = "opt-url";
let fileName;
if (this._isLinkableURL(url)) {
fileName = url.slice(url.lastIndexOf("/") + 1);
node.classList.add("debugger-link");
node.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + url);
node.addEventListener("click", () => gToolbox.viewSourceInDebugger(url, line));
}
fileName = fileName || url || "";
node.textContent = fileName ? `@${fileName}` : "";
return node;
},
/**
* Updates the headers with the current frame's data.
*/
_setHeaders: function (frameData) {
let isMeta = frameData.isMetaCategory;
let name = isMeta ? frameData.categoryData.label : frameData.functionName;
let url = isMeta ? "" : frameData.url;
let line = isMeta ? "" : frameData.line;
this.$headerName.textContent = name;
this.$headerLine.textContent = line;
this._createDebuggerLinkNode(url, line, this.$headerFile);
this.$headerLine.hidden = isMeta;
this.$headerFile.hidden = isMeta;
},
/**
* Takes a string and returns a boolean indicating whether or not
* this is a valid url for linking to the debugger.
*
* @param {String} url
* @return {Boolean}
*/
_isLinkableURL: function (url) {
return url && url.indexOf &&
(url.indexOf("http") === 0 ||
url.indexOf("resource://") === 0 ||
url.indexOf("file://") === 0);
},
/**
* Called when `devtools.theme` changes.
*/
_onThemeChanged: function (_, theme) {
this.graph.setTheme(theme);
this.graph.refresh({ force: true });
},
toString: () => "[object OptimizationsListView]"
};
EventEmitter.decorate(OptimizationsListView);