From 2060dc1512c43bbf314b17fc03fc32f0b874a49b Mon Sep 17 00:00:00 2001 From: "mikhail.naganov@gmail.com" Date: Fri, 24 Apr 2009 11:37:38 +0000 Subject: [PATCH] Added ProfileView object for performing sorting, searching and filtering operations on a profile. It will be used both in the new tickprocessor and Dev Tools profiler. Review URL: http://codereview.chromium.org/92120 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1786 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- test/mjsunit/tools/profile.js | 3 +- test/mjsunit/tools/profileview.js | 95 +++++++++++++++ tools/profile.js | 128 +++++++++++++++++---- tools/profileview.js | 184 ++++++++++++++++++++++++++++++ 4 files changed, 388 insertions(+), 22 deletions(-) create mode 100644 test/mjsunit/tools/profileview.js create mode 100644 tools/profileview.js diff --git a/test/mjsunit/tools/profile.js b/test/mjsunit/tools/profile.js index 87ec8fafd5..4a938600be 100644 --- a/test/mjsunit/tools/profile.js +++ b/test/mjsunit/tools/profile.js @@ -270,7 +270,8 @@ function assertNodeWeights(root, path, selfTicks, totalTicks) { counted++; } - var flatProfile = testDriver.profile.getFlatProfile(); + var flatProfile = + testDriver.profile.getFlatProfile().getRoot().exportChildren(); assertEquals(counted, flatProfile.length, 'counted vs. flatProfile'); for (var i = 0; i < flatProfile.length; ++i) { var rec = flatProfile[i]; diff --git a/test/mjsunit/tools/profileview.js b/test/mjsunit/tools/profileview.js new file mode 100644 index 0000000000..d70c30d61b --- /dev/null +++ b/test/mjsunit/tools/profileview.js @@ -0,0 +1,95 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Load source code files from /tools. +// Files: tools/profile.js tools/profileview.js + + +function createNode(name, time, opt_parent) { + var node = new devtools.profiler.ProfileView.Node(name, time, time, null); + if (opt_parent) { + opt_parent.addChild(node); + } + return node; +} + + +(function testSorting() { + // + // Build a tree: + // root +--c/5 + // | | + // +--a/2 +--b/3--+--d/4 + // | | | + // +--a/1--+--c/1 +--d/2 + // | | + // +--c/1 +--b/2 + // + // So we can sort it using 2 fields: name and time. + var root = createNode('root', 0); + createNode('a', 2, root); + var a1 = createNode('a', 1, root); + createNode('c', 1, root); + var b3 = createNode('b', 3, a1); + createNode('c', 1, a1); + createNode('b', 2, a1); + createNode('c', 5, b3); + createNode('d', 4, b3); + createNode('d', 2, b3); + + var view = new devtools.profiler.ProfileView(root); + var flatTree = []; + + function fillFlatTree(node) { + flatTree.push(node.internalFuncName); + flatTree.push(node.selfTime); + } + + view.traverse(fillFlatTree); + assertEquals( + ['root', 0, + 'a', 2, 'a', 1, 'c', 1, + 'b', 3, 'c', 1, 'b', 2, + 'c', 5, 'd', 4, 'd', 2], flatTree); + + function cmpStrs(s1, s2) { + return s1 == s2 ? 0 : (s1 < s2 ? -1 : 1); + } + + view.sort(function(n1, n2) { + return cmpStrs(n1.internalFuncName, n2.internalFuncName) || + (n1.selfTime - n2.selfTime); + }); + + flatTree = []; + view.traverse(fillFlatTree); + assertEquals( + ['root', 0, + 'a', 1, 'a', 2, 'c', 1, + 'b', 2, 'b', 3, 'c', 1, + 'c', 5, 'd', 2, 'd', 4], flatTree); +})(); diff --git a/tools/profile.js b/tools/profile.js index e70d244de6..d2b53223c0 100644 --- a/tools/profile.js +++ b/tools/profile.js @@ -55,16 +55,33 @@ devtools.profiler.Profile.prototype.skipThisFunction = function(name) { }; +/** + * Enum for profiler operations that involve looking up existing + * code entries. + * + * @enum {number} + */ +devtools.profiler.Profile.Operation = { + MOVE: 0, + DELETE: 1, + TICK: 2 +}; + + /** * Called whenever the specified operation has failed finding a function * containing the specified address. Should be overriden by subclasses. - * Operation is one of the following: 'move', 'delete', 'tick'. + * See the devtools.profiler.Profile.Operation enum for the list of + * possible operations. * - * @param {string} operation Operation name. + * @param {number} operation Operation. * @param {number} addr Address of the unknown code. + * @param {number} opt_stackPos If an unknown address is encountered + * during stack strace processing, specifies a position of the frame + * containing the address. */ devtools.profiler.Profile.prototype.handleUnknownCode = function( - operation, addr) { + operation, addr, opt_stackPos) { }; @@ -77,8 +94,10 @@ devtools.profiler.Profile.prototype.handleUnknownCode = function( */ devtools.profiler.Profile.prototype.addStaticCode = function( name, startAddr, endAddr) { - this.codeMap_.addStaticCode(startAddr, - new devtools.profiler.CodeMap.CodeEntry(endAddr - startAddr, name)); + var entry = new devtools.profiler.CodeMap.CodeEntry( + endAddr - startAddr, name); + this.codeMap_.addStaticCode(startAddr, entry); + return entry; }; @@ -92,8 +111,9 @@ devtools.profiler.Profile.prototype.addStaticCode = function( */ devtools.profiler.Profile.prototype.addCode = function( type, name, start, size) { - this.codeMap_.addCode(start, - new devtools.profiler.Profile.DynamicCodeEntry(size, type, name)); + var entry = new devtools.profiler.Profile.DynamicCodeEntry(size, type, name); + this.codeMap_.addCode(start, entry); + return entry; }; @@ -107,7 +127,7 @@ devtools.profiler.Profile.prototype.moveCode = function(from, to) { try { this.codeMap_.moveCode(from, to); } catch (e) { - this.handleUnknownCode('move', from); + this.handleUnknownCode(devtools.profiler.Profile.Operation.MOVE, from); } }; @@ -121,7 +141,7 @@ devtools.profiler.Profile.prototype.deleteCode = function(start) { try { this.codeMap_.deleteCode(start); } catch (e) { - this.handleUnknownCode('delete', start); + this.handleUnknownCode(devtools.profiler.Profile.Operation.DELETE, start); } }; @@ -156,7 +176,8 @@ devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) { result.push(name); } } else { - this.handleUnknownCode('tick', stack[i]); + this.handleUnknownCode( + devtools.profiler.Profile.Operation.TICK, stack[i], i); } } return result; @@ -168,7 +189,7 @@ devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) { */ devtools.profiler.Profile.prototype.getTopDownTreeRoot = function() { this.topDownTree_.computeTotalWeights(); - return this.topDownTree_.root_; + return this.topDownTree_.getRoot(); }; @@ -177,7 +198,7 @@ devtools.profiler.Profile.prototype.getTopDownTreeRoot = function() { */ devtools.profiler.Profile.prototype.getBottomUpTreeRoot = function() { this.bottomUpTree_.computeTotalWeights(); - return this.bottomUpTree_.root_; + return this.bottomUpTree_.getRoot(); }; @@ -201,13 +222,43 @@ devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) { }; +/** + * Calculates a top down profile starting from the specified node. + * + * @param {devtools.profiler.CallTree.Node} opt_root Starting node. + */ +devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_root) { + if (!opt_root) { + this.topDownTree_.computeTotalWeights(); + return this.topDownTree_; + } else { + throw Error('not implemented'); + } +}; + + +/** + * Calculates a bottom up profile starting from the specified node. + * + * @param {devtools.profiler.CallTree.Node} opt_root Starting node. + */ +devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_root) { + if (!opt_root) { + this.bottomUpTree_.computeTotalWeights(); + return this.bottomUpTree_; + } else { + throw Error('not implemented'); + } +}; + + /** * Calculates a flat profile of callees starting from the specified node. * * @param {devtools.profiler.CallTree.Node} opt_root Starting node. */ devtools.profiler.Profile.prototype.getFlatProfile = function(opt_root) { - var counters = new devtools.profiler.CallTree.Node(''); + var counters = new devtools.profiler.CallTree(); var precs = {}; this.topDownTree_.computeTotalWeights(); this.topDownTree_.traverseInDepth( @@ -226,7 +277,7 @@ devtools.profiler.Profile.prototype.getFlatProfile = function(opt_root) { precs[node.label]--; }, opt_root); - return counters.exportChildren(); + return counters; }; @@ -275,6 +326,14 @@ devtools.profiler.CallTree = function() { devtools.profiler.CallTree.prototype.totalsComputed_ = false; +/** + * Returns the tree root. + */ +devtools.profiler.CallTree.prototype.getRoot = function() { + return this.root_; +}; + + /** * Adds the specified call path, constructing nodes as necessary. * @@ -293,6 +352,20 @@ devtools.profiler.CallTree.prototype.addPath = function(path) { }; +/** + * Finds an immediate child of the specified parent with the specified + * label, creates a child node if necessary. If a parent node isn't + * specified, uses tree root. + * + * @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); +}; + + /** * Computes total weights in the call graph. */ @@ -306,17 +379,30 @@ devtools.profiler.CallTree.prototype.computeTotalWeights = function() { /** - * Traverses the call graph in preorder. + * Traverses the call graph in preorder. This function can be used for + * building optionally modified tree clones. This is the boilerplate code + * for this scenario: * - * @param {function(devtools.profiler.CallTree.Node)} f Visitor function. + * callTree.traverse(function(node, parentClone) { + * var nodeClone = cloneNode(node); + * if (parentClone) + * parentClone.addChild(nodeClone); + * return nodeClone; + * }); + * + * @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) { - var nodesToVisit = [opt_start || this.root_]; - while (nodesToVisit.length > 0) { - var node = nodesToVisit.shift(); - f(node); - nodesToVisit = nodesToVisit.concat(node.exportChildren()); + var pairsToProcess = [{node: opt_start || this.root_, param: null}]; + while (pairsToProcess.length > 0) { + var pair = pairsToProcess.shift(); + var node = pair.node; + var newParam = f(node, pair.param); + node.forEachChild( + function (child) { pairsToProcess.push({node: child, param: newParam}); } + ); } }; diff --git a/tools/profileview.js b/tools/profileview.js new file mode 100644 index 0000000000..53ef6d9e8c --- /dev/null +++ b/tools/profileview.js @@ -0,0 +1,184 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Initlialize namespaces +var devtools = devtools || {}; +devtools.profiler = devtools.profiler || {}; + + +/** + * Creates a Profile View builder object. + * + * @param {number} samplingRate Number of ms between profiler ticks. + * @constructor + */ +devtools.profiler.ViewBuilder = function(samplingRate) { + this.samplingRate = samplingRate; +}; + + +/** + * Builds a profile view for the specified call tree. + * + * @param {devtools.profiler.CallTree} callTree A call tree. + */ +devtools.profiler.ViewBuilder.prototype.buildView = function( + callTree) { + var head; + var samplingRate = this.samplingRate; + callTree.traverse(function(node, viewParent) { + var viewNode = new devtools.profiler.ProfileView.Node( + node.label, node.totalWeight * samplingRate, + node.selfWeight * samplingRate, head); + if (viewParent) { + viewParent.addChild(viewNode); + } else { + head = viewNode; + } + return viewNode; + }); + var view = new devtools.profiler.ProfileView(head); + return view; +}; + + +/** + * Creates a Profile View object. It allows to perform sorting + * and filtering actions on the profile. Profile View mimicks + * the Profile object from WebKit's JSC profiler. + * + * @param {devtools.profiler.ProfileView.Node} head Head (root) node. + * @constructor + */ +devtools.profiler.ProfileView = function(head) { + this.head = head; +}; + + +/** + * Sorts the profile view using the specified sort function. + * + * @param {function(devtools.profiler.ProfileView.Node, + * devtools.profiler.ProfileView.Node):number} sortFunc A sorting + * functions. Must comply with Array.sort sorting function requirements. + */ +devtools.profiler.ProfileView.prototype.sort = function(sortFunc) { + this.traverse(function (node) { + node.sortChildren(sortFunc); + }); +}; + + +/** + * Traverses profile view nodes in preorder. + * + * @param {function(devtools.profiler.ProfileView.Node)} f Visitor function. + */ +devtools.profiler.ProfileView.prototype.traverse = function(f) { + var nodesToTraverse = [this.head]; + while (nodesToTraverse.length > 0) { + var node = nodesToTraverse.shift(); + f(node); + nodesToTraverse = nodesToTraverse.concat(node.children); + } +}; + + +/** + * Constructs a Profile View node object. Each node object corresponds to + * a function call. + * + * @param {string} internalFuncName A fully qualified function name. + * @param {number} totalTime Amount of time that application spent in the + * corresponding function and its descendants (not that depending on + * profile they can be either callees or callers.) + * @param {number} selfTime Amount of time that application spent in the + * corresponding function only. + * @param {devtools.profiler.ProfileView.Node} head Profile view head. + * @constructor + */ +devtools.profiler.ProfileView.Node = function( + internalFuncName, totalTime, selfTime, head) { + this.internalFuncName = internalFuncName; + this.totalTime = totalTime; + this.selfTime = selfTime; + this.head = head; + this.parent = null; + this.children = []; +}; + + +/** + * Returns a share of the function's total time in application's total time. + */ +devtools.profiler.ProfileView.Node.prototype.__defineGetter__( + 'totalPercent', + function() { return this.totalTime / + (this.head ? this.head.totalTime : this.totalTime) * 100.0; }); + + +/** + * Returns a share of the function's self time in application's total time. + */ +devtools.profiler.ProfileView.Node.prototype.__defineGetter__( + 'selfPercent', + function() { return this.selfTime / + (this.head ? this.head.totalTime : this.totalTime) * 100.0; }); + + +/** + * Returns a share of the function's total time in its parent's total time. + */ +devtools.profiler.ProfileView.Node.prototype.__defineGetter__( + 'parentTotalPercent', + function() { return this.totalTime / + (this.parent ? this.parent.totalTime : this.totalTime) * 100.0; }); + + +/** + * Adds a child to the node. + * + * @param {devtools.profiler.ProfileView.Node} node Child node. + */ +devtools.profiler.ProfileView.Node.prototype.addChild = function(node) { + node.parent = this; + this.children.push(node); +}; + + +/** + * Sorts all the node's children recursively. + * + * @param {function(devtools.profiler.ProfileView.Node, + * devtools.profiler.ProfileView.Node):number} sortFunc A sorting + * functions. Must comply with Array.sort sorting function requirements. + */ +devtools.profiler.ProfileView.Node.prototype.sortChildren = function( + sortFunc) { + this.children.sort(sortFunc); +};