this.leave();
this.leave();
this.leave();
+ this.enter('lib2-f1');
+ this.enter('lib1-f1');
+ this.leave();
+ this.leave();
this.stay();
this.leave();
};
Driver.prototype.enter = function(func) {
this.namesTopDown.push(func);
this.namesBottomUp.unshift(func);
- assertNoPathExists(this.profile.getTopDownTreeRoot(), this.namesTopDown,
+ assertNoPathExists(this.profile.getTopDownProfile().getRoot(), this.namesTopDown,
'pre enter/topDown');
- assertNoPathExists(this.profile.getBottomUpTreeRoot(), this.namesBottomUp,
+ assertNoPathExists(this.profile.getBottomUpProfile().getRoot(), this.namesBottomUp,
'pre enter/bottomUp');
Driver.superClass_.enter.call(this, func);
- assertPathExists(this.profile.getTopDownTreeRoot(), this.namesTopDown,
+ assertPathExists(this.profile.getTopDownProfile().getRoot(), this.namesTopDown,
'post enter/topDown');
- assertPathExists(this.profile.getBottomUpTreeRoot(), this.namesBottomUp,
+ assertPathExists(this.profile.getBottomUpProfile().getRoot(), this.namesBottomUp,
'post enter/bottomUp');
};
testDriver.execute();
var pathWeights = [
- [['lib1-f1'], 1, 14],
- [['lib1-f1', 'lib1-f2'], 2, 13],
+ [['lib1-f1'], 1, 16],
+ [['lib1-f1', 'lib1-f2'], 2, 15],
[['lib1-f1', 'lib1-f2', 'T: F1'], 2, 11],
[['lib1-f1', 'lib1-f2', 'T: F1', 'T: F2'], 1, 1],
[['lib1-f1', 'lib1-f2', 'T: F1', 'lib2-f1'], 2, 3],
[['lib1-f1', 'lib1-f2', 'T: F1', 'T: F3'], 1, 5],
[['lib1-f1', 'lib1-f2', 'T: F1', 'T: F3', 'T: F3'], 1, 4],
[['lib1-f1', 'lib1-f2', 'T: F1', 'T: F3', 'T: F3', 'T: F3'], 1, 1],
- [['lib1-f1', 'lib1-f2', 'T: F1', 'T: F3', 'T: F3', 'T: F2'], 2, 2]
+ [['lib1-f1', 'lib1-f2', 'T: F1', 'T: F3', 'T: F3', 'T: F2'], 2, 2],
+ [['lib1-f1', 'lib1-f2', 'lib2-f1'], 1, 2],
+ [['lib1-f1', 'lib1-f2', 'lib2-f1', 'lib1-f1'], 1, 1]
];
- var root = testDriver.profile.getTopDownTreeRoot();
+ var root = testDriver.profile.getTopDownProfile().getRoot();
for (var i = 0; i < pathWeights.length; ++i) {
var data = pathWeights[i];
assertNodeWeights(root, data[0], data[1], data[2]);
ProfileTestDriver.call(this);
this.namesTopDown = [''];
this.counters = {};
+ this.root = null;
};
Inherits(Driver, ProfileTestDriver);
this.namesTopDown.pop();
};
+ Driver.prototype.extractRoot = function() {
+ assertTrue('' in this.counters);
+ this.root = this.counters[''];
+ delete this.counters[''];
+ };
+
var testDriver = new Driver();
testDriver.execute();
+ testDriver.extractRoot();
var counted = 0;
for (var c in testDriver.counters) {
counted++;
}
- var flatProfile =
- testDriver.profile.getFlatProfile().getRoot().exportChildren();
+ var flatProfileRoot = testDriver.profile.getFlatProfile().getRoot();
+ assertEquals(testDriver.root.self, flatProfileRoot.selfWeight);
+ assertEquals(testDriver.root.total, flatProfileRoot.totalWeight);
+
+ var flatProfile = flatProfileRoot.exportChildren();
assertEquals(counted, flatProfile.length, 'counted vs. flatProfile');
for (var i = 0; i < flatProfile.length; ++i) {
var rec = flatProfile[i];
}
})();
+
+
+(function testFunctionCalleesProfileTicks() {
+ var testDriver = new ProfileTestDriver();
+ testDriver.execute();
+
+ var pathWeights = [
+ [['lib2-f1'], 3, 5],
+ [['lib2-f1', 'lib2-f1'], 1, 1],
+ [['lib2-f1', 'lib1-f1'], 1, 1]
+ ];
+
+ var profile = testDriver.profile.getTopDownProfile('lib2-f1');
+ var root = profile.getRoot();
+ for (var i = 0; i < pathWeights.length; ++i) {
+ var data = pathWeights[i];
+ assertNodeWeights(root, data[0], data[1], data[2]);
+ }
+})();
+
+
+(function testFunctionFlatProfileTicks() {
+ var testDriver = new ProfileTestDriver();
+ testDriver.execute();
+
+ var flatWeights = {
+ 'lib2-f1': [1, 1],
+ 'lib1-f1': [1, 1]
+ };
+
+ var flatProfileRoot =
+ testDriver.profile.getFlatProfile('lib2-f1').findOrAddChild('lib2-f1');
+ assertEquals(3, flatProfileRoot.selfWeight);
+ assertEquals(5, flatProfileRoot.totalWeight);
+
+ var flatProfile = flatProfileRoot.exportChildren();
+ assertEquals(2, flatProfile.length, 'counted vs. flatProfile');
+ for (var i = 0; i < flatProfile.length; ++i) {
+ var rec = flatProfile[i];
+ assertTrue(rec.label in flatWeights, 'uncounted: ' + rec.label);
+ var reference = flatWeights[rec.label];
+ assertEquals(reference[0], rec.selfWeight, 'self of ' + rec.label);
+ assertEquals(reference[1], rec.totalWeight, 'total of ' + rec.label);
+ }
+
+})();
+
/**
- * Returns the root of the top down call graph.
- */
-devtools.profiler.Profile.prototype.getTopDownTreeRoot = function() {
- this.topDownTree_.computeTotalWeights();
- return this.topDownTree_.getRoot();
-};
-
-
-/**
- * Returns the root of the bottom up call graph.
- */
-devtools.profiler.Profile.prototype.getBottomUpTreeRoot = function() {
- this.bottomUpTree_.computeTotalWeights();
- return this.bottomUpTree_.getRoot();
-};
-
-
-/**
- * Traverses the top down call graph in preorder.
+ * Performs a BF traversal of the top down call graph.
*
* @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
*/
/**
- * Traverses the bottom up call graph in preorder.
+ * Performs a BF traversal of the bottom up call graph.
*
* @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
*/
/**
- * Calculates a top down profile starting from the specified node.
+ * Calculates a top down profile for a node with the specified label.
+ * If no name specified, returns the whole top down calls tree.
*
- * @param {devtools.profiler.CallTree.Node} opt_root Starting node.
+ * @param {string} opt_label Node label.
*/
-devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_root) {
- if (!opt_root) {
- this.topDownTree_.computeTotalWeights();
- return this.topDownTree_;
- } else {
- throw Error('not implemented');
- }
+devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_label) {
+ return this.getTreeProfile_(this.topDownTree_, opt_label);
+};
+
+
+/**
+ * Calculates a bottom up profile for a node with the specified label.
+ * If no name specified, returns the whole bottom up calls tree.
+ *
+ * @param {string} opt_label Node label.
+ */
+devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_label) {
+ return this.getTreeProfile_(this.bottomUpTree_, opt_label);
};
/**
- * Calculates a bottom up profile starting from the specified node.
+ * Helper function for calculating a tree profile.
*
- * @param {devtools.profiler.CallTree.Node} opt_root Starting node.
+ * @param {devtools.profiler.Profile.CallTree} tree Call tree.
+ * @param {string} opt_label Node label.
*/
-devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_root) {
- if (!opt_root) {
- this.bottomUpTree_.computeTotalWeights();
- return this.bottomUpTree_;
+devtools.profiler.Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
+ if (!opt_label) {
+ tree.computeTotalWeights();
+ return tree;
} else {
- throw Error('not implemented');
+ var subTree = tree.cloneSubtree(opt_label);
+ subTree.computeTotalWeights();
+ return subTree;
}
};
/**
- * Calculates a flat profile of callees starting from the specified node.
+ * Calculates a flat profile of callees starting from a node with
+ * the specified label. If no name specified, starts from the root.
*
- * @param {devtools.profiler.CallTree.Node} opt_root Starting node.
+ * @param {string} opt_label Starting node label.
*/
-devtools.profiler.Profile.prototype.getFlatProfile = function(opt_root) {
+devtools.profiler.Profile.prototype.getFlatProfile = function(opt_label) {
var counters = new devtools.profiler.CallTree();
+ var rootLabel = opt_label || devtools.profiler.CallTree.ROOT_NODE_LABEL;
var precs = {};
+ precs[rootLabel] = 0;
+ var root = counters.findOrAddChild(rootLabel);
+
this.topDownTree_.computeTotalWeights();
this.topDownTree_.traverseInDepth(
function onEnter(node) {
if (!(node.label in precs)) {
precs[node.label] = 0;
}
- var rec = counters.findOrAddChild(node.label);
- rec.selfWeight += node.selfWeight;
- if (precs[node.label] == 0) {
- rec.totalWeight += node.totalWeight;
+ var nodeLabelIsRootLabel = node.label == rootLabel;
+ if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
+ if (precs[rootLabel] == 0) {
+ root.selfWeight += node.selfWeight;
+ root.totalWeight += node.totalWeight;
+ } else {
+ var rec = root.findOrAddChild(node.label);
+ rec.selfWeight += node.selfWeight;
+ if (nodeLabelIsRootLabel || precs[node.label] == 0) {
+ rec.totalWeight += node.totalWeight;
+ }
+ }
+ precs[node.label]++;
}
- precs[node.label]++;
},
function onExit(node) {
- precs[node.label]--;
+ if (node.label == rootLabel || precs[rootLabel] > 0) {
+ precs[node.label]--;
+ }
},
- opt_root);
+ null);
+
+ if (!opt_label) {
+ // If we have created a flat profile for the whole program, we don't
+ // need an explicit root in it. Thus, replace the counters tree
+ // root with the node corresponding to the whole program.
+ counters.root_ = root;
+ } else {
+ // Propagate weights so percents can be calculated correctly.
+ counters.getRoot().selfWeight = root.selfWeight;
+ counters.getRoot().totalWeight = root.totalWeight;
+ }
return counters;
};
* @constructor
*/
devtools.profiler.CallTree = function() {
- this.root_ = new devtools.profiler.CallTree.Node('');
+ this.root_ = new devtools.profiler.CallTree.Node(
+ devtools.profiler.CallTree.ROOT_NODE_LABEL);
};
+/**
+ * The label of the root node.
+ */
+devtools.profiler.CallTree.ROOT_NODE_LABEL = '';
+
+
/**
* @private
*/
*
* @param {string} label Child node label.
*/
-devtools.profiler.CallTree.prototype.findOrAddChild = function(
- label, opt_parent) {
- var parent = opt_parent || this.root_;
- return parent.findOrAddChild(label);
+devtools.profiler.CallTree.prototype.findOrAddChild = function(label) {
+ return this.root_.findOrAddChild(label);
+};
+
+
+/**
+ * Creates a subtree by cloning and merging all subtrees rooted at nodes
+ * with a given label. E.g. cloning the following call tree on label 'A'
+ * will give the following result:
+ *
+ * <A>--<B> <B>
+ * / /
+ * <root> == clone on 'A' ==> <root>--<A>
+ * \ \
+ * <C>--<A>--<D> <D>
+ *
+ * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
+ * source call tree.
+ *
+ * @param {string} label The label of the new root node.
+ */
+devtools.profiler.CallTree.prototype.cloneSubtree = function(label) {
+ var subTree = new devtools.profiler.CallTree();
+ this.traverse(function(node, parent) {
+ if (!parent && node.label != label) {
+ return null;
+ }
+ var child = (parent ? parent : subTree).findOrAddChild(node.label);
+ child.selfWeight += node.selfWeight;
+ return child;
+ });
+ return subTree;
};
*
* @param {function(devtools.profiler.CallTree.Node, *)} f Visitor function.
* The second parameter is the result of calling 'f' on the parent node.
- * @param {devtools.profiler.CallTree.Node} opt_start Starting node.
*/
-devtools.profiler.CallTree.prototype.traverse = function(f, opt_start) {
+devtools.profiler.CallTree.prototype.traverse = function(f) {
var pairsToProcess = new ConsArray();
- pairsToProcess.concat([{node: opt_start || this.root_, param: null}]);
+ pairsToProcess.concat([{node: this.root_, param: null}]);
while (!pairsToProcess.atEnd()) {
var pair = pairsToProcess.next();
var node = pair.node;
* prior to visiting node's children.
* @param {function(devtools.profiler.CallTree.Node)} exit A function called
* after visiting node's children.
- * @param {devtools.profiler.CallTree.Node} opt_start Starting node.
*/
-devtools.profiler.CallTree.prototype.traverseInDepth = function(
- enter, exit, opt_start) {
+devtools.profiler.CallTree.prototype.traverseInDepth = function(enter, exit) {
function traverse(node) {
enter(node);
node.forEachChild(traverse);
exit(node);
}
- traverse(opt_start || this.root_);
+ traverse(this.root_);
};
*
* @param {string} label Child node label.
*/
-devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(
- label) {
+devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(label) {
return this.findChild(label) || this.addChild(label);
};
*/
devtools.profiler.ProfileView = function(head) {
this.head = head;
+ this.title = '';
+ this.uid = '';
+ this.heavyProfile = null;
+ this.treeProfile = null;
+ this.flatProfile = null;
+};
+
+
+/**
+ * Updates references between profiles. This is needed for WebKit
+ * ProfileView.
+ */
+devtools.profiler.ProfileView.prototype.updateProfilesRefs = function() {
+ var profileNames = ["treeProfile", "heavyProfile", "flatProfile"];
+ for (var i = 0; i < profileNames.length; ++i) {
+ var destProfile = this[profileNames[i]];
+ for (var j = 0; j < profileNames.length; ++j) {
+ destProfile[profileNames[j]] = this[profileNames[j]];
+ }
+ }
};
};
+/**
+ * Sorts the profile view by self time, ascending.
+ */
+devtools.profiler.ProfileView.prototype.sortSelfTimeAscending = function() {
+ this.sort(function (node1, node2) {
+ return node1.selfTime - node2.selfTime; });
+};
+
+
+/**
+ * Sorts the profile view by self time, descending.
+ */
+devtools.profiler.ProfileView.prototype.sortSelfTimeDescending = function() {
+ this.sort(function (node1, node2) {
+ return node2.selfTime - node1.selfTime; });
+};
+
+
+/**
+ * Sorts the profile view by total time, ascending.
+ */
+devtools.profiler.ProfileView.prototype.sortTotalTimeAscending = function() {
+ this.sort(function (node1, node2) {
+ return node1.totalTime - node2.totalTime; });
+};
+
+
+/**
+ * Sorts the profile view by total time, descending.
+ */
+devtools.profiler.ProfileView.prototype.sortTotalTimeDescending = function() {
+ this.sort(function (node1, node2) {
+ return node2.totalTime - node1.totalTime; });
+};
+
+
+/**
+ * String comparator compatible with Array.sort requirements.
+ *
+ * @param {string} s1 First string.
+ * @param {string} s2 Second string.
+ */
+devtools.profiler.ProfileView.compareStrings = function(s1, s2) {
+ return s1 < s2 ? -1 : (s1 > s2 ? 1 : 0);
+};
+
+
+/**
+ * Sorts the profile view by function name, ascending.
+ */
+devtools.profiler.ProfileView.prototype.sortFunctionNameAscending = function() {
+ this.sort(function (node1, node2) {
+ return devtools.profiler.ProfileView.compareStrings(
+ node1.functionName, node2.functionName); });
+};
+
+
+/**
+ * Sorts the profile view by function name, descending.
+ */
+devtools.profiler.ProfileView.prototype.sortFunctionNameDescending = function() {
+ this.sort(function (node1, node2) {
+ return devtools.profiler.ProfileView.compareStrings(
+ node2.functionName, node1.functionName); });
+};
+
+
/**
* Traverses profile view nodes in preorder.
*
*/
devtools.profiler.ProfileView.Node = function(
internalFuncName, totalTime, selfTime, head) {
+ this.callIdentifier = 0;
this.internalFuncName = internalFuncName;
+ this.initFuncInfo();
this.totalTime = totalTime;
this.selfTime = selfTime;
this.head = head;
this.parent = null;
this.children = [];
+ this.visible = true;
+};
+
+
+/**
+ * RegEx for stripping V8's prefixes of compiled functions.
+ */
+devtools.profiler.ProfileView.Node.FUNC_NAME_STRIP_RE =
+ /^(?:LazyCompile|Function): (.*)$/;
+
+
+/**
+ * RegEx for extracting script source URL and line number.
+ */
+devtools.profiler.ProfileView.Node.FUNC_NAME_PARSE_RE = /^([^ ]+) (.*):(\d+)$/;
+
+
+/**
+ * RegEx for removing protocol name from URL.
+ */
+devtools.profiler.ProfileView.Node.URL_PARSE_RE = /^(?:http:\/)?.*\/([^/]+)$/;
+
+
+/**
+ * Inits 'functionName', 'url', and 'lineNumber' fields using 'internalFuncName'
+ * field.
+ */
+devtools.profiler.ProfileView.Node.prototype.initFuncInfo = function() {
+ var nodeAlias = devtools.profiler.ProfileView.Node;
+ this.functionName = this.internalFuncName;
+
+ var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName);
+ if (strippedName) {
+ this.functionName = strippedName[1];
+ }
+
+ var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName);
+ if (parsedName) {
+ this.url = parsedName[2];
+ var parsedUrl = nodeAlias.URL_PARSE_RE.exec(this.url);
+ if (parsedUrl) {
+ this.url = parsedUrl[1];
+ }
+ this.functionName = parsedName[1];
+ this.lineNumber = parsedName[3];
+ } else {
+ this.url = '';
+ this.lineNumber = 0;
+ }
};
// A namespace stub. It will become more clear how to declare it properly
// during integration of this script into Dev Tools.
-goog = { structs: {} };
+var goog = goog || {};
+goog.structs = goog.structs || {};
/**
this.printCounter(this.ticks_.unaccounted, this.ticks_.total);
}
+ // Disable initialization of 'funcName', 'url', 'lineNumber' as
+ // we don't use it and it just wastes time.
+ devtools.profiler.ProfileView.Node.prototype.initFuncInfo = function() {};
+
var flatProfile = this.profile_.getFlatProfile();
var flatView = this.viewBuilder_.buildView(flatProfile);
// Sort by self time, desc, then by name, desc.
profile, filterP, func) {
for (var i = 0, n = profile.length; i < n; ++i) {
var rec = profile[i];
- // An empty record corresponds to a tree root.
- if (!rec.internalFuncName || !filterP(rec.internalFuncName)) {
+ if (!filterP(rec.internalFuncName)) {
continue;
}
func(rec);