Skip to content

Commit

Permalink
Bug 1138928 - Display only function name and file, instead of full ur…
Browse files Browse the repository at this point in the history
…l, in flame graphs. r=vp
  • Loading branch information
jsantell committed Apr 7, 2015
1 parent 5f2d40a commit e9926a9
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 127 deletions.
1 change: 1 addition & 0 deletions browser/devtools/shared/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ EXTRA_JS_MODULES.devtools += [
]

EXTRA_JS_MODULES.devtools.shared.profiler += [
'profiler/frame-utils.js',
'profiler/global.js',
'profiler/jit.js',
'profiler/tree-model.js',
Expand Down
131 changes: 131 additions & 0 deletions browser/devtools/shared/profiler/frame-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* 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 { Ci } = require("chrome");
const { extend } = require("sdk/util/object");
loader.lazyRequireGetter(this, "Services");
loader.lazyRequireGetter(this, "CATEGORY_OTHER",
"devtools/shared/profiler/global", true);

// The cache used in the `nsIURL` function.
const gNSURLStore = new Map();
const CHROME_SCHEMES = ["chrome://", "resource://", "jar:file://"];
const CONTENT_SCHEMES = ["http://", "https://", "file://", "app://"];

/**
* Parses the raw location of this function call to retrieve the actual
* function name, source url, host name, line and column.
*/
exports.parseLocation = function parseLocation (frame) {
// Parse the `location` for the function name, source url, line, column etc.
let lineAndColumn = frame.location.match(/((:\d+)*)\)?$/)[1];
let [, line, column] = lineAndColumn.split(":");
line = line || frame.line;
column = column || frame.column;

let firstParenIndex = frame.location.indexOf("(");
let lineAndColumnIndex = frame.location.indexOf(lineAndColumn);
let resource = frame.location.substring(firstParenIndex + 1, lineAndColumnIndex);

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 = frame.location.substring(0, firstParenIndex - 1);
fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/";
hostName = url.indexOf("jar:") == 0 ? "" : uri.host;
} else {
functionName = frame.location;
url = null;
}

return {
functionName: functionName,
fileName: fileName,
hostName: hostName,
url: url,
line: line,
column: column
};
},

/**
* 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.
*/
exports.isContent = 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)));
}

/**
* This filters out platform data frames in a sample. With latest performance
* tool in Fx40, when displaying only content, we still filter out all platform data,
* except we generalize platform data that are leaves. We do this because of two
* observations:
*
* 1. The leaf is where time is _actually_ being spent, so we _need_ to show it
* to developers in some way to give them accurate profiling data. We decide to
* split the platform into various category buckets and just show time spent in
* each bucket.
*
* 2. The calls leading to the leaf _aren't_ where we are spending time, but
* _do_ give the developer context for how they got to the leaf where they _are_
* spending time. For non-platform hackers, the non-leaf platform frames don't
* give any meaningful context, and so we can safely filter them out.
*
* Example transformations:
* Before: PlatformA -> PlatformB -> ContentA -> ContentB
* After: ContentA -> ContentB
*
* Before: PlatformA -> ContentA -> PlatformB -> PlatformC
* After: ContentA -> Category(PlatformC)
*/
exports.filterPlatformData = function filterPlatformData (frames) {
let result = [];
let last = frames.length - 1;
let frame;

for (let i = 0; i < frames.length; i++) {
frame = frames[i];
if (exports.isContent(frame)) {
result.push(frame);
} else if (last === i) {
// Extend here so we're not destructively editing
// the original profiler data. Set isMetaCategory `true`,
// and ensure we have a category set by default, because that's how
// the generalized frame nodes are organized.
result.push(extend({ isMetaCategory: true, category: CATEGORY_OTHER }, frame));
}
}

return result;
}

/**
* 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;
}
134 changes: 10 additions & 124 deletions browser/devtools/shared/profiler/tree-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
"use strict";

const {Cc, Ci, Cu, Cr} = require("chrome");
const {extend} = require("sdk/util/object");

loader.lazyRequireGetter(this, "Services");
loader.lazyRequireGetter(this, "L10N",
"devtools/shared/profiler/global", true);
loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
Expand All @@ -17,15 +15,12 @@ loader.lazyRequireGetter(this, "CATEGORY_JIT",
"devtools/shared/profiler/global", true);
loader.lazyRequireGetter(this, "JITOptimizations",
"devtools/shared/profiler/jit", true);
loader.lazyRequireGetter(this, "CATEGORY_OTHER",
"devtools/shared/profiler/global", true);

const CHROME_SCHEMES = ["chrome://", "resource://", "jar:file://"];
const CONTENT_SCHEMES = ["http://", "https://", "file://", "app://"];
loader.lazyRequireGetter(this, "FrameUtils",
"devtools/shared/profiler/frame-utils");

exports.ThreadNode = ThreadNode;
exports.FrameNode = FrameNode;
exports.FrameNode.isContent = isContent;
exports.FrameNode.isContent = FrameUtils.isContent;

/**
* A call tree for a thread. This is essentially a linkage between all frames
Expand Down Expand Up @@ -102,7 +97,7 @@ ThreadNode.prototype = {
// should be taken into consideration.
if (options.contentOnly) {
// The (root) node is not considered a content function, it'll be removed.
sampleFrames = filterPlatformData(sampleFrames);
sampleFrames = FrameUtils.filterPlatformData(sampleFrames);
} else {
// Remove the (root) node manually.
sampleFrames = sampleFrames.slice(1);
Expand Down Expand Up @@ -253,42 +248,13 @@ FrameNode.prototype = {
// default to an "unknown" category otherwise.
let categoryData = CATEGORY_MAPPINGS[this.category] || {};

// Parse the `location` for the function name, source url, line, column etc.
let lineAndColumn = this.location.match(/((:\d+)*)\)?$/)[1];
let [, line, column] = lineAndColumn.split(":");
line = line || this.line;
column = column || this.column;

let firstParenIndex = this.location.indexOf("(");
let lineAndColumnIndex = this.location.indexOf(lineAndColumn);
let resource = this.location.substring(firstParenIndex + 1, lineAndColumnIndex);

let url = resource.split(" -> ").pop();
let uri = nsIURL(url);
let functionName, fileName, hostName;
let parsedData = FrameUtils.parseLocation(this);
parsedData.nodeType = "Frame";
parsedData.categoryData = categoryData;
parsedData.isContent = FrameUtils.isContent(this);
parsedData.isMetaCategory = this.isMetaCategory;

// If the URI digged out from the `location` is valid, this is a JS frame.
if (uri) {
functionName = this.location.substring(0, firstParenIndex - 1);
fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/";
hostName = url.indexOf("jar:") == 0 ? "" : uri.host;
} else {
functionName = this.location;
url = null;
}

return this._data = {
nodeType: "Frame",
functionName: functionName,
fileName: fileName,
hostName: hostName,
url: url,
line: line,
column: column,
categoryData: categoryData,
isContent: !!isContent(this),
isMetaCategory: this.isMetaCategory
};
return this._data = parsedData;
},

/**
Expand All @@ -310,83 +276,3 @@ FrameNode.prototype = {
return this._optimizations;
}
};

/**
* 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();

/**
* This filters out platform data frames in a sample. With latest performance
* tool in Fx40, when displaying only content, we still filter out all platform data,
* except we generalize platform data that are leaves. We do this because of two
* observations:
*
* 1. The leaf is where time is _actually_ being spent, so we _need_ to show it
* to developers in some way to give them accurate profiling data. We decide to
* split the platform into various category buckets and just show time spent in
* each bucket.
*
* 2. The calls leading to the leaf _aren't_ where we are spending time, but
* _do_ give the developer context for how they got to the leaf where they _are_
* spending time. For non-platform hackers, the non-leaf platform frames don't
* give any meaningful context, and so we can safely filter them out.
*
* Example transformations:
* Before: PlatformA -> PlatformB -> ContentA -> ContentB
* After: ContentA -> ContentB
*
* Before: PlatformA -> ContentA -> PlatformB -> PlatformC
* After: ContentA -> Category(PlatformC)
*/
function filterPlatformData (frames) {
let result = [];
let last = frames.length - 1;
let frame;

for (let i = 0; i < frames.length; i++) {
frame = frames[i];
if (isContent(frame)) {
result.push(frame);
} else if (last === i) {
// Extend here so we're not destructively editing
// the original profiler data. Set isMetaCategory `true`,
// and ensure we have a category set by default, because that's how
// the generalized frame nodes are organized.
result.push(extend({ isMetaCategory: true, category: CATEGORY_OTHER }, frame));
}
}

return result;
}
1 change: 1 addition & 0 deletions browser/devtools/shared/test/browser.ini
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ support-files =
[browser_flame-graph-utils-03.js]
[browser_flame-graph-utils-04.js]
[browser_flame-graph-utils-05.js]
[browser_flame-graph-utils-06.js]
[browser_flame-graph-utils-hash.js]
[browser_graphs-01.js]
[browser_graphs-02.js]
Expand Down
Loading

0 comments on commit e9926a9

Please sign in to comment.