Index: test/mjsunit/tools/profile.js |
diff --git a/test/mjsunit/tools/profile.js b/test/mjsunit/tools/profile.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..87ec8fafd51c2724b941e62705338576e375d60c |
--- /dev/null |
+++ b/test/mjsunit/tools/profile.js |
@@ -0,0 +1,283 @@ |
+// 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 <project root>/tools. |
+// Files: tools/splaytree.js tools/codemap.js tools/profile.js |
+ |
+ |
+function stackToString(stack) { |
+ return stack.join(' -> '); |
+}; |
+ |
+ |
+function assertPathExists(root, path, opt_message) { |
+ var message = opt_message ? ' (' + opt_message + ')' : ''; |
+ assertNotNull(root.descendToChild(path, function(node, pos) { |
+ assertNotNull(node, |
+ stackToString(path.slice(0, pos)) + ' has no child ' + |
+ path[pos] + message); |
+ }), opt_message); |
+}; |
+ |
+ |
+function assertNoPathExists(root, path, opt_message) { |
+ var message = opt_message ? ' (' + opt_message + ')' : ''; |
+ assertNull(root.descendToChild(path), opt_message); |
+}; |
+ |
+ |
+function countNodes(profile, traverseFunc) { |
+ var count = 0; |
+ traverseFunc.call(profile, function () { count++; }); |
+ return count; |
+}; |
+ |
+ |
+function ProfileTestDriver() { |
+ this.profile = new devtools.profiler.Profile(); |
+ this.stack_ = []; |
+ this.addFunctions_(); |
+}; |
+ |
+ |
+// Addresses inside functions. |
+ProfileTestDriver.prototype.funcAddrs_ = { |
+ 'lib1-f1': 0x11110, 'lib1-f2': 0x11210, |
+ 'lib2-f1': 0x21110, 'lib2-f2': 0x21210, |
+ 'T: F1': 0x50110, 'T: F2': 0x50210, 'T: F3': 0x50410 }; |
+ |
+ |
+ProfileTestDriver.prototype.addFunctions_ = function() { |
+ this.profile.addStaticCode('lib1', 0x11000, 0x12000); |
+ this.profile.addStaticCode('lib1-f1', 0x11100, 0x11900); |
+ this.profile.addStaticCode('lib1-f2', 0x11200, 0x11500); |
+ this.profile.addStaticCode('lib2', 0x21000, 0x22000); |
+ this.profile.addStaticCode('lib2-f1', 0x21100, 0x21900); |
+ this.profile.addStaticCode('lib2-f2', 0x21200, 0x21500); |
+ this.profile.addCode('T', 'F1', 0x50100, 0x100); |
+ this.profile.addCode('T', 'F2', 0x50200, 0x100); |
+ this.profile.addCode('T', 'F3', 0x50400, 0x100); |
+}; |
+ |
+ |
+ProfileTestDriver.prototype.enter = function(funcName) { |
+ // Stack looks like this: [pc, caller, ..., main]. |
+ // Therefore, we are adding entries at the beginning. |
+ this.stack_.unshift(this.funcAddrs_[funcName]); |
+ this.profile.recordTick(this.stack_); |
+}; |
+ |
+ |
+ProfileTestDriver.prototype.stay = function() { |
+ this.profile.recordTick(this.stack_); |
+}; |
+ |
+ |
+ProfileTestDriver.prototype.leave = function() { |
+ this.stack_.shift(); |
+}; |
+ |
+ |
+ProfileTestDriver.prototype.execute = function() { |
+ this.enter('lib1-f1'); |
+ this.enter('lib1-f2'); |
+ this.enter('T: F1'); |
+ this.enter('T: F2'); |
+ this.leave(); |
+ this.stay(); |
+ this.enter('lib2-f1'); |
+ this.enter('lib2-f1'); |
+ this.leave(); |
+ this.stay(); |
+ this.leave(); |
+ this.enter('T: F3'); |
+ this.enter('T: F3'); |
+ this.enter('T: F3'); |
+ this.leave(); |
+ this.enter('T: F2'); |
+ this.stay(); |
+ this.leave(); |
+ this.leave(); |
+ this.leave(); |
+ this.leave(); |
+ this.stay(); |
+ this.leave(); |
+}; |
+ |
+ |
+function Inherits(childCtor, parentCtor) { |
+ function tempCtor() {}; |
+ tempCtor.prototype = parentCtor.prototype; |
+ childCtor.superClass_ = parentCtor.prototype; |
+ childCtor.prototype = new tempCtor(); |
+ childCtor.prototype.constructor = childCtor; |
+}; |
+ |
+ |
+(function testCallTreeBuilding() { |
+ function Driver() { |
+ ProfileTestDriver.call(this); |
+ this.namesTopDown = []; |
+ this.namesBottomUp = []; |
+ }; |
+ Inherits(Driver, ProfileTestDriver); |
+ |
+ Driver.prototype.enter = function(func) { |
+ this.namesTopDown.push(func); |
+ this.namesBottomUp.unshift(func); |
+ assertNoPathExists(this.profile.getTopDownTreeRoot(), this.namesTopDown, |
+ 'pre enter/topDown'); |
+ assertNoPathExists(this.profile.getBottomUpTreeRoot(), this.namesBottomUp, |
+ 'pre enter/bottomUp'); |
+ Driver.superClass_.enter.call(this, func); |
+ assertPathExists(this.profile.getTopDownTreeRoot(), this.namesTopDown, |
+ 'post enter/topDown'); |
+ assertPathExists(this.profile.getBottomUpTreeRoot(), this.namesBottomUp, |
+ 'post enter/bottomUp'); |
+ }; |
+ |
+ Driver.prototype.stay = function() { |
+ var preTopDownNodes = countNodes(this.profile, this.profile.traverseTopDownTree); |
+ var preBottomUpNodes = countNodes(this.profile, this.profile.traverseBottomUpTree); |
+ Driver.superClass_.stay.call(this); |
+ var postTopDownNodes = countNodes(this.profile, this.profile.traverseTopDownTree); |
+ var postBottomUpNodes = countNodes(this.profile, this.profile.traverseBottomUpTree); |
+ // Must be no changes in tree layout. |
+ assertEquals(preTopDownNodes, postTopDownNodes, 'stay/topDown'); |
+ assertEquals(preBottomUpNodes, postBottomUpNodes, 'stay/bottomUp'); |
+ }; |
+ |
+ Driver.prototype.leave = function() { |
+ Driver.superClass_.leave.call(this); |
+ this.namesTopDown.pop(); |
+ this.namesBottomUp.shift(); |
+ }; |
+ |
+ var testDriver = new Driver(); |
+ testDriver.execute(); |
+})(); |
+ |
+ |
+function assertNodeWeights(root, path, selfTicks, totalTicks) { |
+ var node = root.descendToChild(path); |
+ var stack = stackToString(path); |
+ assertNotNull(node, 'node not found: ' + stack); |
+ assertEquals(selfTicks, node.selfWeight, 'self of ' + stack); |
+ assertEquals(totalTicks, node.totalWeight, 'total of ' + stack); |
+}; |
+ |
+ |
+(function testTopDownRootProfileTicks() { |
+ var testDriver = new ProfileTestDriver(); |
+ testDriver.execute(); |
+ |
+ var pathWeights = [ |
+ [['lib1-f1'], 1, 14], |
+ [['lib1-f1', 'lib1-f2'], 2, 13], |
+ [['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', 'lib2-f1', 'lib2-f1'], 1, 1], |
+ [['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] |
+ ]; |
+ |
+ var root = testDriver.profile.getTopDownTreeRoot(); |
+ for (var i = 0; i < pathWeights.length; ++i) { |
+ var data = pathWeights[i]; |
+ assertNodeWeights(root, data[0], data[1], data[2]); |
+ } |
+})(); |
+ |
+ |
+(function testRootFlatProfileTicks() { |
+ function Driver() { |
+ ProfileTestDriver.call(this); |
+ this.namesTopDown = ['']; |
+ this.counters = {}; |
+ }; |
+ Inherits(Driver, ProfileTestDriver); |
+ |
+ Driver.prototype.increment = function(func, self, total) { |
+ if (!(func in this.counters)) { |
+ this.counters[func] = { self: 0, total: 0 }; |
+ } |
+ this.counters[func].self += self; |
+ this.counters[func].total += total; |
+ }; |
+ |
+ Driver.prototype.incrementTotals = function() { |
+ // Only count each function in the stack once. |
+ var met = {}; |
+ for (var i = 0; i < this.namesTopDown.length; ++i) { |
+ var name = this.namesTopDown[i]; |
+ if (!(name in met)) { |
+ this.increment(name, 0, 1); |
+ } |
+ met[name] = true; |
+ } |
+ }; |
+ |
+ Driver.prototype.enter = function(func) { |
+ Driver.superClass_.enter.call(this, func); |
+ this.namesTopDown.push(func); |
+ this.increment(func, 1, 0); |
+ this.incrementTotals(); |
+ }; |
+ |
+ Driver.prototype.stay = function() { |
+ Driver.superClass_.stay.call(this); |
+ this.increment(this.namesTopDown[this.namesTopDown.length - 1], 1, 0); |
+ this.incrementTotals(); |
+ }; |
+ |
+ Driver.prototype.leave = function() { |
+ Driver.superClass_.leave.call(this); |
+ this.namesTopDown.pop(); |
+ }; |
+ |
+ var testDriver = new Driver(); |
+ testDriver.execute(); |
+ |
+ var counted = 0; |
+ for (var c in testDriver.counters) { |
+ counted++; |
+ } |
+ |
+ var flatProfile = testDriver.profile.getFlatProfile(); |
+ assertEquals(counted, flatProfile.length, 'counted vs. flatProfile'); |
+ for (var i = 0; i < flatProfile.length; ++i) { |
+ var rec = flatProfile[i]; |
+ assertTrue(rec.label in testDriver.counters, 'uncounted: ' + rec.label); |
+ var reference = testDriver.counters[rec.label]; |
+ assertEquals(reference.self, rec.selfWeight, 'self of ' + rec.label); |
+ assertEquals(reference.total, rec.totalWeight, 'total of ' + rec.label); |
+ } |
+ |
+})(); |