Enhancing profiling data processing code with functionality needed for the Dev Tools...
authormikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 30 Apr 2009 08:10:27 +0000 (08:10 +0000)
committermikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 30 Apr 2009 08:10:27 +0000 (08:10 +0000)
Details:

 - added properties / functions in view objects needed for WebKit's ProfileView;

 - added ability to count profiles for specific functions.

The tickprocessor functionality does not affected.

Review URL: http://codereview.chromium.org/99181

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1823 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

test/mjsunit/tools/profile.js
tools/profile.js
tools/profile_view.js
tools/splaytree.js
tools/tickprocessor.js

index aaa9aa76c1850d2e01c5afc1986c236d31c3a858..49eef3b0aaa329ec54399e6d7c489762e96c3191 100644 (file)
@@ -124,6 +124,10 @@ ProfileTestDriver.prototype.execute = function() {
           this.leave();
         this.leave();
       this.leave();
+      this.enter('lib2-f1');
+        this.enter('lib1-f1');
+        this.leave();
+      this.leave();
     this.stay();
   this.leave();
 };
@@ -149,14 +153,14 @@ function Inherits(childCtor, parentCtor) {
   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');
   };
 
@@ -196,8 +200,8 @@ function assertNodeWeights(root, path, selfTicks, totalTicks) {
   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],
@@ -205,10 +209,12 @@ function assertNodeWeights(root, path, selfTicks, totalTicks) {
     [['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]);
@@ -221,6 +227,7 @@ function assertNodeWeights(root, path, selfTicks, totalTicks) {
     ProfileTestDriver.call(this);
     this.namesTopDown = [''];
     this.counters = {};
+    this.root = null;
   };
   Inherits(Driver, ProfileTestDriver);
 
@@ -262,16 +269,26 @@ function assertNodeWeights(root, path, selfTicks, totalTicks) {
     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];
@@ -282,3 +299,50 @@ function assertNodeWeights(root, path, selfTicks, totalTicks) {
   }
 
 })();
+
+
+(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);
+  }
+
+})();
+
index 70e30846da9ebef5dc135c234d8a9defee5fbadb..614c63557697b5a73858d38d0af8026e4e7b78ea 100644 (file)
@@ -185,25 +185,7 @@ devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
 
 
 /**
- * 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.
  */
@@ -213,7 +195,7 @@ devtools.profiler.Profile.prototype.traverseTopDownTree = function(f) {
 
 
 /**
- * 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.
  */
@@ -223,60 +205,96 @@ devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) {
 
 
 /**
- * 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;
 };
 
@@ -316,10 +334,17 @@ devtools.profiler.Profile.DynamicCodeEntry.prototype.getName = function() {
  * @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
  */
@@ -359,10 +384,38 @@ devtools.profiler.CallTree.prototype.addPath = function(path) {
  *
  * @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;
 };
 
 
@@ -392,11 +445,10 @@ devtools.profiler.CallTree.prototype.computeTotalWeights = function() {
  *
  * @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;
@@ -416,16 +468,14 @@ devtools.profiler.CallTree.prototype.traverse = function(f, opt_start) {
  *     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_);
 };
 
 
@@ -507,8 +557,7 @@ devtools.profiler.CallTree.Node.prototype.findChild = function(label) {
  *
  * @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);
 };
 
index 8cffccc7ca2d4edef5a66e7726918347f0f162e1..cd0511f6bae9e619dcf891bffa2ee4546bea4e21 100644 (file)
@@ -77,6 +77,26 @@ devtools.profiler.ViewBuilder.prototype.buildView = function(
  */
 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]];
+    }
+  }
 };
 
 
@@ -94,6 +114,73 @@ devtools.profiler.ProfileView.prototype.sort = function(sortFunc) {
 };
 
 
+/**
+ * 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.
  *
@@ -125,12 +212,63 @@ devtools.profiler.ProfileView.prototype.traverse = function(f) {
  */
 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;
+  }
 };
 
 
index f3beb164ae8921cb29ff42145daf1e890485f0e5..7b3af8b992a2d11f2b75a5e020b07c498b189564 100644 (file)
@@ -28,7 +28,8 @@
 
 // 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 || {};
 
 
 /**
index 9d19719713fafc3acf62e999b36e61b7405a46ac..64020cabbcae83aa4af45c9d7d318b7a706695f2 100644 (file)
@@ -273,6 +273,10 @@ TickProcessor.prototype.printStatistics = function() {
     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.
@@ -361,8 +365,7 @@ TickProcessor.prototype.processProfile = function(
     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);