mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
271 lines
8.3 KiB
JavaScript
271 lines
8.3 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";
|
|
|
|
const {Cc, Ci, Cu, Cr} = require("chrome");
|
|
|
|
loader.lazyRequireGetter(this, "Services");
|
|
loader.lazyRequireGetter(this, "L10N",
|
|
"devtools/profiler/global", true);
|
|
loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
|
|
"devtools/profiler/global", true);
|
|
loader.lazyRequireGetter(this, "CATEGORY_JIT",
|
|
"devtools/profiler/global", true);
|
|
|
|
const CHROME_SCHEMES = ["chrome://", "resource://"];
|
|
const CONTENT_SCHEMES = ["http://", "https://", "file://"];
|
|
|
|
exports.ThreadNode = ThreadNode;
|
|
exports.FrameNode = FrameNode;
|
|
exports._isContent = isContent; // used in tests
|
|
|
|
/**
|
|
* A call tree for a thread. This is essentially a linkage between all frames
|
|
* of all samples into a single tree structure, with additional information
|
|
* on each node, like the time spent (in milliseconds) and samples count.
|
|
*
|
|
* Example:
|
|
* {
|
|
* duration: number,
|
|
* calls: {
|
|
* "FunctionName (url:line)": {
|
|
* line: number,
|
|
* category: number,
|
|
* samples: number,
|
|
* duration: number,
|
|
* calls: {
|
|
* ...
|
|
* }
|
|
* }, // FrameNode
|
|
* ...
|
|
* }
|
|
* } // ThreadNode
|
|
*
|
|
* @param object threadSamples
|
|
* The raw samples array received from the backend.
|
|
* @param boolean contentOnly [optional]
|
|
* @see ThreadNode.prototype.insert
|
|
* @param number beginAt [optional]
|
|
* @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, invert) {
|
|
this.samples = 0;
|
|
this.duration = 0;
|
|
this.calls = {};
|
|
this._previousSampleTime = 0;
|
|
|
|
for (let sample of threadSamples) {
|
|
this.insert(sample, contentOnly, beginAt, endAt, invert);
|
|
}
|
|
}
|
|
|
|
ThreadNode.prototype = {
|
|
/**
|
|
* Adds function calls in the tree from a sample's frames.
|
|
*
|
|
* @param object sample
|
|
* The { frames, time } sample, containing an array of frames and
|
|
* the time the sample was taken. This sample is assumed to be older
|
|
* than the most recently inserted one.
|
|
* @param boolean contentOnly [optional]
|
|
* Specifies if platform frames shouldn't be taken into consideration.
|
|
* @param number beginAt [optional]
|
|
* 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, inverted = false) {
|
|
let sampleTime = sample.time;
|
|
if (!sampleTime || sampleTime < beginAt || sampleTime > endAt) {
|
|
return;
|
|
}
|
|
|
|
let sampleFrames = sample.frames;
|
|
|
|
// Filter out platform frames if only content-related function calls
|
|
// should be taken into consideration.
|
|
if (contentOnly) {
|
|
// The (root) node is not considered a content function, it'll be removed.
|
|
sampleFrames = sampleFrames.filter(isContent);
|
|
} else {
|
|
// Remove the (root) node manually.
|
|
sampleFrames = sampleFrames.slice(1);
|
|
}
|
|
if (!sampleFrames.length) {
|
|
return;
|
|
}
|
|
if (inverted) {
|
|
sampleFrames.reverse();
|
|
}
|
|
|
|
let sampleDuration = sampleTime - this._previousSampleTime;
|
|
this._previousSampleTime = sampleTime;
|
|
this.samples++;
|
|
this.duration += sampleDuration;
|
|
|
|
FrameNode.prototype.insert(
|
|
sampleFrames, 0, sampleTime, sampleDuration, this.calls);
|
|
},
|
|
|
|
/**
|
|
* Gets additional details about this node.
|
|
* @return object
|
|
*/
|
|
getInfo: function() {
|
|
return {
|
|
nodeType: "Thread",
|
|
functionName: L10N.getStr("table.root"),
|
|
categoryData: {}
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A function call node in a tree.
|
|
*
|
|
* @param string location
|
|
* The location of this function call. Note that this isn't sanitized,
|
|
* so it may very well (not?) include the function name, url, etc.
|
|
* @param number line
|
|
* The line number inside the source containing this function call.
|
|
* @param number category
|
|
* The category type of this function call ("js", "graphics" etc.).
|
|
*/
|
|
function FrameNode({ location, line, category }) {
|
|
this.location = location;
|
|
this.line = line;
|
|
this.category = category;
|
|
this.sampleTimes = [];
|
|
this.samples = 0;
|
|
this.duration = 0;
|
|
this.calls = {};
|
|
}
|
|
|
|
FrameNode.prototype = {
|
|
/**
|
|
* Adds function calls in the tree from a sample's frames. For example, given
|
|
* the the frames below (which would account for three calls to `insert` on
|
|
* the root frame), the following tree structure is created:
|
|
*
|
|
* A
|
|
* A -> B -> C / \
|
|
* A -> B -> D ~> B E
|
|
* A -> E -> F / \ \
|
|
* C D F
|
|
* @param frames
|
|
* The sample call stack.
|
|
* @param index
|
|
* The index of the call in the stack representing this node.
|
|
* @param number time
|
|
* The delta time (in milliseconds) when the frame was sampled.
|
|
* @param number duration
|
|
* The amount of time spent executing all functions on the stack.
|
|
*/
|
|
insert: function(frames, index, time, duration, _store = this.calls) {
|
|
let frame = frames[index];
|
|
if (!frame) {
|
|
return;
|
|
}
|
|
let location = frame.location;
|
|
let child = _store[location] || (_store[location] = new FrameNode(frame));
|
|
child.sampleTimes.push({ start: time, end: time + duration });
|
|
child.samples++;
|
|
child.duration += duration;
|
|
child.insert(frames, ++index, time, duration);
|
|
},
|
|
|
|
/**
|
|
* Parses the raw location of this function call to retrieve the actual
|
|
* function name and source url.
|
|
*
|
|
* @return object
|
|
* The computed { name, file, url, line } properties for this
|
|
* function call.
|
|
*/
|
|
getInfo: function() {
|
|
// "EnterJIT" pseudoframes are special, not actually on the stack.
|
|
if (this.location == "EnterJIT") {
|
|
this.category = CATEGORY_JIT;
|
|
}
|
|
|
|
// Since only C++ stack frames have associated category information,
|
|
// default to an "unknown" category otherwise.
|
|
let categoryData = CATEGORY_MAPPINGS[this.category] || {};
|
|
|
|
// Parse the `location` for the function name, source url and line.
|
|
let firstParen = this.location.indexOf("(");
|
|
let lastColon = this.location.lastIndexOf(":");
|
|
let resource = this.location.substring(firstParen + 1, lastColon);
|
|
let line = this.location.substring(lastColon + 1).replace(")", "");
|
|
let url = resource.split(" -> ").pop();
|
|
let uri = nsIURL(url);
|
|
let functionName, fileName, hostName;
|
|
|
|
// If the URI digged out from the `location` is valid, this is a JS frame.
|
|
if (uri) {
|
|
functionName = this.location.substring(0, firstParen - 1);
|
|
fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/";
|
|
hostName = uri.host;
|
|
} else {
|
|
functionName = this.location;
|
|
url = null;
|
|
line = null;
|
|
}
|
|
|
|
return {
|
|
nodeType: "Frame",
|
|
functionName: functionName,
|
|
fileName: fileName,
|
|
hostName: hostName,
|
|
url: url,
|
|
line: line || this.line,
|
|
categoryData: categoryData,
|
|
isContent: !!isContent(this)
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Checks if the specified function represents a chrome or content frame.
|
|
*
|
|
* @param object frame
|
|
* The { category, location } properties of the frame.
|
|
* @return boolean
|
|
* True if a content frame, false if a chrome frame.
|
|
*/
|
|
function isContent({ category, location }) {
|
|
// Only C++ stack frames have associated category information.
|
|
return !category &&
|
|
!CHROME_SCHEMES.find(e => location.contains(e)) &&
|
|
CONTENT_SCHEMES.find(e => location.contains(e));
|
|
}
|
|
|
|
/**
|
|
* Helper for getting an nsIURL instance out of a string.
|
|
*/
|
|
function nsIURL(url) {
|
|
let cached = gNSURLStore.get(url);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
let uri = null;
|
|
try {
|
|
uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
|
|
} catch(e) {
|
|
// The passed url string is invalid.
|
|
}
|
|
gNSURLStore.set(url, uri);
|
|
return uri;
|
|
}
|
|
|
|
// The cache used in the `nsIURL` function.
|
|
let gNSURLStore = new Map();
|