Files
tubestation/browser/devtools/performance/views/jit-optimizations.js

428 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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";
const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
const OPTIMIZATION_FAILURE = L10N.getStr("jit.optimizationFailure");
const JIT_SAMPLES = L10N.getStr("jit.samples2");
const JIT_EMPTY_TEXT = L10N.getStr("jit.empty");
const PROPNAME_MAX_LENGTH = 4;
/**
* View for rendering JIT Optimization data. The terminology and types
* used here can be referenced:
* @see browser/devtools/performance/modules/logic/jit.js
*/
let JITOptimizationsView = {
_currentFrame: null,
/**
* Initialization function called when the tool starts up.
*/
initialize: function () {
this.reset = this.reset.bind(this);
this._onFocusFrame = this._onFocusFrame.bind(this);
this._toggleVisibility = this._toggleVisibility.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
});
// Start the tree by resetting.
this.reset();
this._toggleVisibility();
PerformanceController.on(EVENTS.RECORDING_SELECTED, this.reset);
PerformanceController.on(EVENTS.PREF_CHANGED, this._toggleVisibility);
JsCallTreeView.on("focus", this._onFocusFrame);
},
/**
* Destruction function called when the tool cleans up.
*/
destroy: function () {
this.tree = null;
this.$headerName = this.$headerFile = this.$headerLine = this.el = null;
PerformanceController.off(EVENTS.RECORDING_SELECTED, this.reset);
PerformanceController.off(EVENTS.PREF_CHANGED, this._toggleVisibility);
JsCallTreeView.off("focus", this._onFocusFrame);
},
/**
* Takes a FrameNode, with corresponding optimization data to be displayed
* in the view.
*
* @param {FrameNode} frameNode
*/
setCurrentFrame: function (frameNode) {
if (frameNode !== this.getCurrentFrame()) {
this._currentFrame = frameNode;
}
},
/**
* Returns the current frame node for this view.
*
* @return {?FrameNode}
*/
getCurrentFrame: function (frameNode) {
return this._currentFrame;
},
/**
* Clears out data in the tree, sets to an empty state,
* and removes current frame.
*/
reset: function () {
this.setCurrentFrame(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();
},
/**
* Helper to determine whether or not this view should be enabled.
*/
isEnabled: function () {
return PerformanceController.getOption("show-jit-optimizations");
},
/**
* 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 () {
if (!this.isEnabled()) {
return;
}
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.emit(EVENTS.OPTIMIZATIONS_RENDERED, 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 browser/devtools/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 browser/devtools/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 browser/devtools/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);
},
/**
* Toggles the visibility of the JITOptimizationsView based on the preference
* devtools.performance.ui.show-jit-optimizations.
*/
_toggleVisibility: function () {
let enabled = this.isEnabled();
this.el.hidden = !enabled;
// If view is toggled on, and there's a frame node selected,
// attempt to render it
if (enabled) {
this.render();
}
},
/**
* Called when the JSCallTreeView focuses on a frame.
*/
_onFocusFrame: function (_, view) {
if (!view.frame) {
return;
}
// Only attempt to rerender if this is new -- focus is called even
// when the window removes focus and comes back, so this prevents
// repeating rendering of the same frame
let shouldRender = this.getCurrentFrame() !== view.frame;
// Save the frame even if the view is disabled, so we can
// render it if it becomes enabled
this.setCurrentFrame(view.frame);
if (shouldRender) {
this.render();
}
},
toString: () => "[object JITOptimizationsView]"
};
EventEmitter.decorate(JITOptimizationsView);