TickProcessor script reimplemented in JavaScript.

This is an effort to reuse profiler data processing code both in
TickProcessor and Dev Tools Profiler. The old Python implementation
will be removed.

The new TickProcessor works almost identical to the previous one.
However, it has some differences:

1. Not very useful "Call profile" section is replaced with a new
   WebKit-like "Bottom up (heavy) profile" which shows the most
   expensive functions together with their callers. I used it
   personally in order to find and remove bottlenecks in the
   tickprocessor script itself, and found it quite helpful.

2. Code entries with duplicate names (they occur for RegExes, stubs
   and sometimes for anonymous Function objects) are now distinguished
   by adding an occurence number inside curly brackets.

3. (Address -> code entry) mapping is more precise in boundary cases.

4. Windows version no more requires specifying .map file location.

5. Works faster.

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1802 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
mikhail.naganov@gmail.com 2009-04-27 13:50:42 +00:00
parent c56b22c72a
commit aa2c33126c
12 changed files with 840 additions and 14 deletions

View File

@ -114,3 +114,13 @@ function assertNoEntry(codeMap, addr) {
assertNoEntry(codeMap, 0x1700);
assertEntry(codeMap, 'code1', 0x1800);
})();
(function testDynamicNamesDuplicates() {
var codeMap = new devtools.profiler.CodeMap();
// Code entries with same names but different addresses.
codeMap.addCode(0x1500, newCodeEntry(0x200, 'code'));
codeMap.addCode(0x1700, newCodeEntry(0x100, 'code'));
assertEntry(codeMap, 'code', 0x1500);
assertEntry(codeMap, 'code {1}', 0x1700);
})();

View File

@ -0,0 +1,60 @@
// 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 ConsArray implementation from <project root>/tools.
// Files: tools/consarray.js
var arr1 = new ConsArray();
assertTrue(arr1.atEnd());
arr1.concat([]);
assertTrue(arr1.atEnd());
arr1.concat([1]);
assertFalse(arr1.atEnd());
assertEquals(1, arr1.next());
assertTrue(arr1.atEnd());
arr1.concat([2, 3, 4]);
arr1.concat([5]);
arr1.concat([]);
arr1.concat([6, 7]);
assertFalse(arr1.atEnd());
assertEquals(2, arr1.next());
assertFalse(arr1.atEnd());
assertEquals(3, arr1.next());
assertFalse(arr1.atEnd());
assertEquals(4, arr1.next());
assertFalse(arr1.atEnd());
assertEquals(5, arr1.next());
assertFalse(arr1.atEnd());
assertEquals(6, arr1.next());
assertFalse(arr1.atEnd());
assertEquals(7, arr1.next());
assertTrue(arr1.atEnd());

View File

@ -26,7 +26,7 @@
// 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
// Files: tools/splaytree.js tools/codemap.js tools/consarray.js tools/profile.js
function stackToString(stack) {

View File

@ -26,7 +26,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Load source code files from <project root>/tools.
// Files: tools/profile.js tools/profileview.js
// Files: tools/consarray.js tools/profile.js tools/profileview.js
function createNode(name, time, opt_parent) {

View File

@ -47,6 +47,8 @@ devtools.profiler.CodeMap = function() {
*/
this.deleted_ = [];
this.dynamicsNameGen_ = new devtools.profiler.CodeMap.NameGenerator();
/**
* Static code entries. Used for libraries code.
*/
@ -79,6 +81,8 @@ devtools.profiler.CodeMap.PAGE_SIZE =
* @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
*/
devtools.profiler.CodeMap.prototype.addCode = function(start, codeEntry) {
var entryName = this.dynamicsNameGen_.getName(codeEntry.name);
codeEntry.name = entryName;
this.dynamics_.insert(start, codeEntry);
};
@ -204,7 +208,22 @@ devtools.profiler.CodeMap.CodeEntry.prototype.getName = function() {
return this.name;
};
devtools.profiler.CodeMap.CodeEntry.prototype.toString = function() {
return this.name + ': ' + this.size.toString(16);
};
devtools.profiler.CodeMap.NameGenerator = function() {
this.knownNames_ = [];
};
devtools.profiler.CodeMap.NameGenerator.prototype.getName = function(name) {
if (!(name in this.knownNames_)) {
this.knownNames_[name] = 0;
return name;
}
var count = ++this.knownNames_[name];
return name + ' {' + count + '}';
};

93
tools/consarray.js Normal file
View File

@ -0,0 +1,93 @@
// 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.
/**
* Constructs a ConsArray object. It is used mainly for tree traversal.
* In this use case we have lots of arrays that we need to iterate
* sequentally. The internal Array implementation is horribly slow
* when concatenating on large (10K items) arrays due to memory copying.
* That's why we avoid copying memory and insead build a linked list
* of arrays to iterate through.
*
* @constructor
*/
function ConsArray() {
this.tail_ = new ConsArray.Cell(null, null);
this.currCell_ = this.tail_;
this.currCellPos_ = 0;
};
/**
* Concatenates another array for iterating. Empty arrays are ignored.
* This operation can be safely performed during ongoing ConsArray
* iteration.
*
* @param {Array} arr Array to concatenate.
*/
ConsArray.prototype.concat = function(arr) {
if (arr.length > 0) {
this.tail_.data = arr;
this.tail_ = this.tail_.next = new ConsArray.Cell(null, null);
}
};
/**
* Whether the end of iteration is reached.
*/
ConsArray.prototype.atEnd = function() {
return this.currCell_ === null ||
this.currCell_.data === null ||
this.currCellPos_ >= this.currCell_.data.length;
};
/**
* Returns the current item, moves to the next one.
*/
ConsArray.prototype.next = function() {
var result = this.currCell_.data[this.currCellPos_++];
if (this.currCellPos_ >= this.currCell_.data.length) {
this.currCell_ = this.currCell_.next;
this.currCellPos_ = 0;
}
return result;
};
/**
* A cell object used for constructing a list in ConsArray.
*
* @constructor
*/
ConsArray.Cell = function(data, next) {
this.data = data;
this.next = next;
};

16
tools/linux-tick-processor Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
tools_dir=$(dirname "$0")
if [ "$1" != "--no-build" ]
then
scons -C $tools_dir/.. d8
else
shift
fi
# nm spits out 'no symbols found' messages on stderr
$tools_dir/../d8 $tools_dir/splaytree.js $tools_dir/codemap.js \
$tools_dir/csvparser.js $tools_dir/consarray.js \
$tools_dir/profile.js $tools_dir/profileview.js \
$tools_dir/tickprocessor.js -- $@ 2>/dev/null

View File

@ -395,14 +395,16 @@ devtools.profiler.CallTree.prototype.computeTotalWeights = function() {
* @param {devtools.profiler.CallTree.Node} opt_start Starting node.
*/
devtools.profiler.CallTree.prototype.traverse = function(f, opt_start) {
var pairsToProcess = [{node: opt_start || this.root_, param: null}];
while (pairsToProcess.length > 0) {
var pair = pairsToProcess.shift();
var pairsToProcess = new ConsArray();
pairsToProcess.concat([{node: opt_start || this.root_, param: null}]);
while (!pairsToProcess.atEnd()) {
var pair = pairsToProcess.next();
var node = pair.node;
var newParam = f(node, pair.param);
node.forEachChild(
function (child) { pairsToProcess.push({node: child, param: newParam}); }
);
var morePairsToProcess = [];
node.forEachChild(function (child) {
morePairsToProcess.push({node: child, param: newParam}); });
pairsToProcess.concat(morePairsToProcess);
}
};

View File

@ -100,11 +100,12 @@ devtools.profiler.ProfileView.prototype.sort = function(sortFunc) {
* @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();
var nodesToTraverse = new ConsArray();
nodesToTraverse.concat([this.head]);
while (!nodesToTraverse.atEnd()) {
var node = nodesToTraverse.next();
f(node);
nodesToTraverse = nodesToTraverse.concat(node.children);
nodesToTraverse.concat(node.children);
}
};

620
tools/tickprocessor.js Normal file
View File

@ -0,0 +1,620 @@
// 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.
function Profile(separateIc) {
devtools.profiler.Profile.call(this);
if (!separateIc) {
this.skipThisFunction = function(name) { return Profile.IC_RE.test(name); };
}
};
Profile.prototype = devtools.profiler.Profile.prototype;
Profile.IC_RE =
/^(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Call|Load|Store)IC_)/;
/**
* A thin wrapper around shell's 'read' function showing a file name on error.
*/
function readFile(fileName) {
try {
return read(fileName);
} catch (e) {
print(fileName + ': ' + (e.message || e));
throw e;
}
}
function TickProcessor(
cppEntriesProvider, separateIc, ignoreUnknown, stateFilter) {
this.cppEntriesProvider_ = cppEntriesProvider;
this.ignoreUnknown_ = ignoreUnknown;
this.stateFilter_ = stateFilter;
var ticks = this.ticks_ =
{ total: 0, unaccounted: 0, excluded: 0, gc: 0 };
Profile.prototype.handleUnknownCode = function(
operation, addr, opt_stackPos) {
var op = devtools.profiler.Profile.Operation;
switch (operation) {
case op.MOVE:
print('Code move event for unknown code: 0x' + addr.toString(16));
break;
case op.DELETE:
print('Code delete event for unknown code: 0x' + addr.toString(16));
break;
case op.TICK:
// Only unknown PCs (the first frame) are reported as unaccounted,
// otherwise tick balance will be corrupted (this behavior is compatible
// with the original tickprocessor.py script.)
if (opt_stackPos == 0) {
ticks.unaccounted++;
}
break;
}
};
this.profile_ = new Profile(separateIc);
this.codeTypes_ = {};
// Count each tick as a time unit.
this.viewBuilder_ = new devtools.profiler.ViewBuilder(1);
this.lastLogFileName_ = null;
};
TickProcessor.VmStates = {
JS: 0,
GC: 1,
COMPILER: 2,
OTHER: 3,
EXTERNAL: 4
};
TickProcessor.CodeTypes = {
JS: 0,
CPP: 1,
SHARED_LIB: 2
};
TickProcessor.RecordsDispatch = {
'shared-library': { parsers: [null, parseInt, parseInt],
processor: 'processSharedLibrary' },
'code-creation': { parsers: [null, parseInt, parseInt, null],
processor: 'processCodeCreation' },
'code-move': { parsers: [parseInt, parseInt],
processor: 'processCodeMove' },
'code-delete': { parsers: [parseInt], processor: 'processCodeDelete' },
'tick': { parsers: [parseInt, parseInt, parseInt, 'var-args'],
processor: 'processTick' },
'profiler': null,
// Obsolete row types.
'code-allocate': null,
'begin-code-region': null,
'end-code-region': null
};
TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0;
TickProcessor.prototype.setCodeType = function(name, type) {
this.codeTypes_[name] = TickProcessor.CodeTypes[type];
};
TickProcessor.prototype.isSharedLibrary = function(name) {
return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB;
};
TickProcessor.prototype.isCppCode = function(name) {
return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP;
};
TickProcessor.prototype.isJsCode = function(name) {
return this.codeTypes_[name] == TickProcessor.CodeTypes.JS;
};
TickProcessor.prototype.processLogFile = function(fileName) {
this.lastLogFileName_ = fileName;
var contents = readFile(fileName);
this.processLog(contents.split('\n'));
};
TickProcessor.prototype.processLog = function(lines) {
var csvParser = new devtools.profiler.CsvParser();
try {
for (var i = 0, n = lines.length; i < n; ++i) {
var line = lines[i];
if (!line) {
continue;
}
var fields = csvParser.parseLine(line);
this.dispatchLogRow(fields);
}
} catch (e) {
print('line ' + (i + 1) + ': ' + (e.message || e));
throw e;
}
};
TickProcessor.prototype.dispatchLogRow = function(fields) {
// Obtain the dispatch.
var command = fields[0];
if (!(command in TickProcessor.RecordsDispatch)) {
throw new Error('unknown command: ' + command);
}
var dispatch = TickProcessor.RecordsDispatch[command];
if (dispatch === null) {
return;
}
// Parse fields.
var parsedFields = [];
for (var i = 0; i < dispatch.parsers.length; ++i) {
var parser = dispatch.parsers[i];
if (parser === null) {
parsedFields.push(fields[1 + i]);
} else if (typeof parser == 'function') {
parsedFields.push(parser(fields[1 + i]));
} else {
// var-args
parsedFields.push(fields.slice(1 + i));
break;
}
}
// Run the processor.
this[dispatch.processor].apply(this, parsedFields);
};
TickProcessor.prototype.processSharedLibrary = function(
name, startAddr, endAddr) {
var entry = this.profile_.addStaticCode(name, startAddr, endAddr);
this.setCodeType(entry.getName(), 'SHARED_LIB');
var self = this;
var libFuncs = this.cppEntriesProvider_.parseVmSymbols(
name, startAddr, endAddr, function(fName, fStart, fEnd) {
self.profile_.addStaticCode(fName, fStart, fEnd);
self.setCodeType(fName, 'CPP');
});
};
TickProcessor.prototype.processCodeCreation = function(
type, start, size, name) {
var entry = this.profile_.addCode(type, name, start, size);
this.setCodeType(entry.getName(), 'JS');
};
TickProcessor.prototype.processCodeMove = function(from, to) {
this.profile_.moveCode(from, to);
};
TickProcessor.prototype.processCodeDelete = function(start) {
this.profile_.deleteCode(start);
};
TickProcessor.prototype.includeTick = function(vmState) {
return this.stateFilter_ == null || this.stateFilter_ == vmState;
};
TickProcessor.prototype.processTick = function(pc, sp, vmState, stack) {
this.ticks_.total++;
if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++;
if (!this.includeTick(vmState)) {
this.ticks_.excluded++;
return;
}
var fullStack = [pc];
for (var i = 0, n = stack.length; i < n; ++i) {
var frame = stack[i];
// Leave only numbers starting with 0x. Filter possible 'overflow' string.
if (frame.charAt(0) == '0') {
fullStack.push(parseInt(frame, 16));
}
}
this.profile_.recordTick(fullStack);
};
TickProcessor.prototype.printStatistics = function() {
print('Statistical profiling result from ' + this.lastLogFileName_ +
', (' + this.ticks_.total +
' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' +
this.ticks_.excluded + ' excluded).');
if (this.ticks_.total == 0) return;
// Print the unknown ticks percentage if they are not ignored.
if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) {
this.printHeader('Unknown');
this.printCounter(this.ticks_.unaccounted, this.ticks_.total);
}
var flatProfile = this.profile_.getFlatProfile();
var flatView = this.viewBuilder_.buildView(flatProfile);
// Sort by self time, desc, then by name, desc.
flatView.sort(function(rec1, rec2) {
return rec2.selfTime - rec1.selfTime ||
(rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
var totalTicks = this.ticks_.total;
if (this.ignoreUnknown_) {
totalTicks -= this.ticks_.unaccounted;
}
// Our total time contains all the ticks encountered,
// while profile only knows about the filtered ticks.
flatView.head.totalTime = totalTicks;
// Count library ticks
var flatViewNodes = flatView.head.children;
var self = this;
var libraryTicks = 0;
this.processProfile(flatViewNodes,
function(name) { return self.isSharedLibrary(name); },
function(rec) { libraryTicks += rec.selfTime; });
var nonLibraryTicks = totalTicks - libraryTicks;
this.printHeader('Shared libraries');
this.printEntries(flatViewNodes, null,
function(name) { return self.isSharedLibrary(name); });
this.printHeader('JavaScript');
this.printEntries(flatViewNodes, nonLibraryTicks,
function(name) { return self.isJsCode(name); });
this.printHeader('C++');
this.printEntries(flatViewNodes, nonLibraryTicks,
function(name) { return self.isCppCode(name); });
this.printHeader('GC');
this.printCounter(this.ticks_.gc, totalTicks);
this.printHeavyProfHeader();
var heavyProfile = this.profile_.getBottomUpProfile();
var heavyView = this.viewBuilder_.buildView(heavyProfile);
// To show the same percentages as in the flat profile.
heavyView.head.totalTime = totalTicks;
// Sort by total time, desc, then by name, desc.
heavyView.sort(function(rec1, rec2) {
return rec2.totalTime - rec1.totalTime ||
(rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
this.printHeavyProfile(heavyView.head.children);
};
function padLeft(s, len) {
s = s.toString();
if (s.length < len) {
s = (new Array(len - s.length + 1).join(' ')) + s;
}
return s;
};
TickProcessor.prototype.printHeader = function(headerTitle) {
print('\n [' + headerTitle + ']:');
print(' ticks total nonlib name');
};
TickProcessor.prototype.printHeavyProfHeader = function() {
print('\n [Bottom up (heavy) profile]:');
print(' Note: percentage shows a share of a particular caller in the ' +
'total\n' +
' amount of its parent calls.');
print(' Callers occupying less than ' +
TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) +
'% are not shown.\n');
print(' ticks parent name');
};
TickProcessor.prototype.printCounter = function(ticksCount, totalTicksCount) {
var pct = ticksCount * 100.0 / totalTicksCount;
print(' ' + padLeft(ticksCount, 5) + ' ' + padLeft(pct.toFixed(1), 5) + '%');
};
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)) {
continue;
}
func(rec);
}
};
TickProcessor.prototype.printEntries = function(
profile, nonLibTicks, filterP) {
this.processProfile(profile, filterP, function (rec) {
if (rec.selfTime == 0) return;
var nonLibPct = nonLibTicks != null ?
rec.selfTime * 100.0 / nonLibTicks : 0.0;
print(' ' + padLeft(rec.selfTime, 5) + ' ' +
padLeft(rec.selfPercent.toFixed(1), 5) + '% ' +
padLeft(nonLibPct.toFixed(1), 5) + '% ' +
rec.internalFuncName);
});
};
TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) {
var self = this;
var indent = opt_indent || 0;
var indentStr = padLeft('', indent);
this.processProfile(profile, function() { return true; }, function (rec) {
// Cut off too infrequent callers.
if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return;
print(' ' + padLeft(rec.totalTime, 5) + ' ' +
padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' +
indentStr + rec.internalFuncName);
// Limit backtrace depth.
if (indent < 10) {
self.printHeavyProfile(rec.children, indent + 2);
}
// Delimit top-level functions.
if (indent == 0) {
print('');
}
});
};
function CppEntriesProvider() {
};
CppEntriesProvider.prototype.parseVmSymbols = function(
libName, libStart, libEnd, processorFunc) {
var syms = this.loadSymbols(libName);
if (syms.length == 0) return;
var prevEntry;
function addPrevEntry(end) {
// Several functions can be mapped onto the same address. To avoid
// creating zero-sized entries, skip such duplicates.
if (prevEntry && prevEntry.start != end) {
processorFunc(prevEntry.name, prevEntry.start, end);
}
}
for (var i = 0, n = syms.length; i < n; ++i) {
var line = syms[i];
var funcInfo = this.parseLine(line);
if (!funcInfo) {
continue;
}
if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) {
funcInfo.start += libStart;
}
addPrevEntry(funcInfo.start);
prevEntry = funcInfo;
}
addPrevEntry(libEnd);
};
CppEntriesProvider.prototype.loadSymbols = function(libName) {
return [];
};
CppEntriesProvider.prototype.parseLine = function(line) {
return { name: '', start: 0 };
};
function inherits(childCtor, parentCtor) {
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.prototype = new tempCtor();
};
function UnixCppEntriesProvider() {
};
inherits(UnixCppEntriesProvider, CppEntriesProvider);
UnixCppEntriesProvider.FUNC_RE = /^([0-9a-fA-F]{8}) . (.*)$/;
UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
var normalSyms = os.system('nm', ['-C', '-n', libName], -1, -1);
var dynaSyms = os.system('nm', ['-C', '-n', '-D', libName], -1, -1);
var syms = (normalSyms + dynaSyms).split('\n');
return syms;
};
UnixCppEntriesProvider.prototype.parseLine = function(line) {
var fields = line.match(UnixCppEntriesProvider.FUNC_RE);
return fields ? { name: fields[2], start: parseInt(fields[1], 16) } : null;
};
function WindowsCppEntriesProvider() {
};
inherits(WindowsCppEntriesProvider, CppEntriesProvider);
WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.exe$/;
WindowsCppEntriesProvider.FUNC_RE =
/^ 0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/;
WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) {
var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE);
// Only try to load symbols for the .exe file.
if (!fileNameFields) return [];
var mapFileName = fileNameFields[1] + '.map';
return readFile(mapFileName).split('\r\n');
};
WindowsCppEntriesProvider.prototype.parseLine = function(line) {
var fields = line.match(WindowsCppEntriesProvider.FUNC_RE);
return fields ?
{ name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } :
null;
};
/**
* Performs very simple unmangling of C++ names.
*
* Does not handle arguments and template arguments. The mangled names have
* the form:
*
* ?LookupInDescriptor@JSObject@internal@v8@@...arguments info...
*/
WindowsCppEntriesProvider.prototype.unmangleName = function(name) {
// Empty or non-mangled name.
if (name.length < 1 || name.charAt(0) != '?') return name;
var nameEndPos = name.indexOf('@@');
var components = name.substring(1, nameEndPos).split('@');
components.reverse();
return components.join('::');
};
function padRight(s, len) {
s = s.toString();
if (s.length < len) {
s = s + (new Array(len - s.length + 1).join(' '));
}
return s;
};
function processArguments(args) {
var result = {
logFileName: 'v8.log',
platform: 'unix',
stateFilter: null,
ignoreUnknown: false,
separateIc: false
};
var argsDispatch = {
'-j': ['stateFilter', TickProcessor.VmStates.JS,
'Show only ticks from JS VM state'],
'-g': ['stateFilter', TickProcessor.VmStates.GC,
'Show only ticks from GC VM state'],
'-c': ['stateFilter', TickProcessor.VmStates.COMPILER,
'Show only ticks from COMPILER VM state'],
'-o': ['stateFilter', TickProcessor.VmStates.OTHER,
'Show only ticks from OTHER VM state'],
'-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL,
'Show only ticks from EXTERNAL VM state'],
'--ignore-unknown': ['ignoreUnknown', true,
'Exclude ticks of unknown code entries from processing'],
'--separate-ic': ['separateIc', true,
'Separate IC entries'],
'--unix': ['platform', 'unix',
'Specify that we are running on *nix platform'],
'--windows': ['platform', 'windows',
'Specify that we are running on Windows platform']
};
argsDispatch['--js'] = argsDispatch['-j'];
argsDispatch['--gc'] = argsDispatch['-g'];
argsDispatch['--compiler'] = argsDispatch['-c'];
argsDispatch['--other'] = argsDispatch['-o'];
argsDispatch['--external'] = argsDispatch['-e'];
function printUsageAndExit() {
print('Cmdline args: [options] [log-file-name]\n' +
'Default log file name is "v8.log".\n');
print('Options:');
for (var arg in argsDispatch) {
var synonims = [arg];
var dispatch = argsDispatch[arg];
for (var synArg in argsDispatch) {
if (arg !== synArg && dispatch === argsDispatch[synArg]) {
synonims.push(synArg);
delete argsDispatch[synArg];
}
}
print(' ' + padRight(synonims.join(', '), 20) + dispatch[2]);
}
quit(2);
}
while (args.length) {
var arg = args[0];
if (arg.charAt(0) != '-') {
break;
}
args.shift();
if (arg in argsDispatch) {
var dispatch = argsDispatch[arg];
result[dispatch[0]] = dispatch[1];
} else {
printUsageAndExit();
}
}
if (args.length >= 1) {
result.logFileName = args.shift();
}
return result;
};
var params = processArguments(arguments);
var tickProcessor = new TickProcessor(
params.platform == 'unix' ? new UnixCppEntriesProvider() :
new WindowsCppEntriesProvider(),
params.separateIc,
params.ignoreUnknown,
params.stateFilter);
tickProcessor.processLogFile(params.logFileName);
tickProcessor.printStatistics();

View File

@ -411,7 +411,7 @@ class TickProcessor(object):
number_of_accounted_ticks -= self.unaccounted_number_of_ticks
number_of_non_library_ticks = number_of_accounted_ticks - self.number_of_library_ticks
entries.sort(key=lambda e:e.tick_count, reverse=True)
entries.sort(key=lambda e: (e.tick_count, e.ToString()), reverse=True)
for entry in entries:
if entry.tick_count > 0 and condition(entry):
total_percentage = entry.tick_count * 100.0 / number_of_accounted_ticks

View File

@ -0,0 +1,5 @@
@echo off
SET tools_dir=%~dp0
%tools_dir%..\d8 %tools_dir%splaytree.js %tools_dir%codemap.js %tools_dir%csvparser.js %tools_dir%consarray.js %tools_dir%profile.js %tools_dir%profileview.js %tools_dir%tickprocessor.js -- --windows %*