v8/tools/tickprocessor.js
Tobias Tebbi 58b45cca2f [profiler] fix 4 bugs in --prof/linux-tick-processor
- In log.cc, we used InstructionStart() for code create events, but
  the Code object address for code move events. Change to use
  InstructionStart() for both.
- The symbol table contains some kind of virtual address, not file
  offsets. They happened to be identical in the past but are no longer,
  probably due to toolchain changes. Now we use objdump to figure out
  the difference between virtual addresses and file offsets.
- When a new code object happened to be created at the same address as
  a previous one, we wouldn't update it.
  This is indeed wrong, as predicted in a TODO by Jaro.
- For 64bit addresses, using >>> is wrong, now replaced with division.


Change-Id: Ib23114ed736f98bfc33c65004a039a3fd04d3c49
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2016586
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Commit-Queue: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66145}
2020-02-06 10:38:34 +00:00

978 lines
32 KiB
JavaScript

// Copyright 2012 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 inherits(childCtor, parentCtor) {
childCtor.prototype.__proto__ = parentCtor.prototype;
};
function V8Profile(separateIc, separateBytecodes, separateBuiltins,
separateStubs) {
Profile.call(this);
var regexps = [];
if (!separateIc) regexps.push(V8Profile.IC_RE);
if (!separateBytecodes) regexps.push(V8Profile.BYTECODES_RE);
if (!separateBuiltins) regexps.push(V8Profile.BUILTINS_RE);
if (!separateStubs) regexps.push(V8Profile.STUBS_RE);
if (regexps.length > 0) {
this.skipThisFunction = function(name) {
for (var i=0; i<regexps.length; i++) {
if (regexps[i].test(name)) return true;
}
return false;
};
}
};
inherits(V8Profile, Profile);
V8Profile.IC_RE =
/^(LoadGlobalIC: )|(Handler: )|(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Load|Store)IC_)/;
V8Profile.BYTECODES_RE = /^(BytecodeHandler: )/
V8Profile.BUILTINS_RE = /^(Builtin: )/
V8Profile.STUBS_RE = /^(Stub: )/
/**
* A thin wrapper around shell's 'read' function showing a file name on error.
*/
function readFile(fileName) {
try {
return read(fileName);
} catch (e) {
printErr(fileName + ': ' + (e.message || e));
throw e;
}
}
/**
* Parser for dynamic code optimization state.
*/
function parseState(s) {
switch (s) {
case "": return Profile.CodeState.COMPILED;
case "~": return Profile.CodeState.OPTIMIZABLE;
case "*": return Profile.CodeState.OPTIMIZED;
}
throw new Error("unknown code state: " + s);
}
function TickProcessor(
cppEntriesProvider,
separateIc,
separateBytecodes,
separateBuiltins,
separateStubs,
callGraphSize,
ignoreUnknown,
stateFilter,
distortion,
range,
sourceMap,
timedRange,
pairwiseTimedRange,
onlySummary,
runtimeTimerFilter,
preprocessJson) {
this.preprocessJson = preprocessJson;
LogReader.call(this, {
'shared-library': { parsers: [parseString, parseInt, parseInt, parseInt],
processor: this.processSharedLibrary },
'code-creation': {
parsers: [parseString, parseInt, parseInt, parseInt, parseInt,
parseString, parseVarArgs],
processor: this.processCodeCreation },
'code-deopt': {
parsers: [parseInt, parseInt, parseInt, parseInt, parseInt,
parseString, parseString, parseString],
processor: this.processCodeDeopt },
'code-move': { parsers: [parseInt, parseInt, ],
processor: this.processCodeMove },
'code-delete': { parsers: [parseInt],
processor: this.processCodeDelete },
'code-source-info': {
parsers: [parseInt, parseInt, parseInt, parseInt, parseString,
parseString, parseString],
processor: this.processCodeSourceInfo },
'script-source': {
parsers: [parseInt, parseString, parseString],
processor: this.processScriptSource },
'sfi-move': { parsers: [parseInt, parseInt],
processor: this.processFunctionMove },
'active-runtime-timer': {
parsers: [parseString],
processor: this.processRuntimeTimerEvent },
'tick': {
parsers: [parseInt, parseInt, parseInt,
parseInt, parseInt, parseVarArgs],
processor: this.processTick },
'heap-sample-begin': { parsers: [parseString, parseString, parseInt],
processor: this.processHeapSampleBegin },
'heap-sample-end': { parsers: [parseString, parseString],
processor: this.processHeapSampleEnd },
'timer-event-start' : { parsers: [parseString, parseString, parseString],
processor: this.advanceDistortion },
'timer-event-end' : { parsers: [parseString, parseString, parseString],
processor: this.advanceDistortion },
// Ignored events.
'profiler': null,
'function-creation': null,
'function-move': null,
'function-delete': null,
'heap-sample-item': null,
'current-time': null, // Handled specially, not parsed.
// Obsolete row types.
'code-allocate': null,
'begin-code-region': null,
'end-code-region': null },
timedRange,
pairwiseTimedRange);
this.cppEntriesProvider_ = cppEntriesProvider;
this.callGraphSize_ = callGraphSize;
this.ignoreUnknown_ = ignoreUnknown;
this.stateFilter_ = stateFilter;
this.runtimeTimerFilter_ = runtimeTimerFilter;
this.sourceMap = sourceMap;
var ticks = this.ticks_ =
{ total: 0, unaccounted: 0, excluded: 0, gc: 0 };
distortion = parseInt(distortion);
// Convert picoseconds to nanoseconds.
this.distortion_per_entry = isNaN(distortion) ? 0 : (distortion / 1000);
this.distortion = 0;
var rangelimits = range ? range.split(",") : [];
var range_start = parseInt(rangelimits[0]);
var range_end = parseInt(rangelimits[1]);
// Convert milliseconds to nanoseconds.
this.range_start = isNaN(range_start) ? -Infinity : (range_start * 1000);
this.range_end = isNaN(range_end) ? Infinity : (range_end * 1000)
V8Profile.prototype.handleUnknownCode = function(
operation, addr, opt_stackPos) {
var op = Profile.Operation;
switch (operation) {
case op.MOVE:
printErr('Code move event for unknown code: 0x' + addr.toString(16));
break;
case op.DELETE:
printErr('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;
}
};
if (preprocessJson) {
this.profile_ = new JsonProfile();
} else {
this.profile_ = new V8Profile(separateIc, separateBytecodes,
separateBuiltins, separateStubs);
}
this.codeTypes_ = {};
// Count each tick as a time unit.
this.viewBuilder_ = new ViewBuilder(1);
this.lastLogFileName_ = null;
this.generation_ = 1;
this.currentProducerProfile_ = null;
this.onlySummary_ = onlySummary;
};
inherits(TickProcessor, LogReader);
TickProcessor.VmStates = {
JS: 0,
GC: 1,
PARSER: 2,
BYTECODE_COMPILER: 3,
COMPILER: 4,
OTHER: 5,
EXTERNAL: 6,
IDLE: 7,
};
TickProcessor.CodeTypes = {
CPP: 0,
SHARED_LIB: 1
};
// Otherwise, this is JS-related code. We are not adding it to
// codeTypes_ map because there can be zillions of them.
TickProcessor.CALL_PROFILE_CUTOFF_PCT = 1.0;
TickProcessor.CALL_GRAPH_SIZE = 5;
/**
* @override
*/
TickProcessor.prototype.printError = function(str) {
printErr(str);
};
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 name !== "UNKNOWN" && !(name in this.codeTypes_);
};
TickProcessor.prototype.processLogFile = function(fileName) {
this.lastLogFileName_ = fileName;
var line;
while (line = readline()) {
this.processLogLine(line);
}
};
TickProcessor.prototype.processLogFileInTest = function(fileName) {
// Hack file name to avoid dealing with platform specifics.
this.lastLogFileName_ = 'v8.log';
var contents = readFile(fileName);
this.processLogChunk(contents);
};
TickProcessor.prototype.processSharedLibrary = function(
name, startAddr, endAddr, aslrSlide) {
var entry = this.profile_.addLibrary(name, startAddr, endAddr, aslrSlide);
this.setCodeType(entry.getName(), 'SHARED_LIB');
var self = this;
var libFuncs = this.cppEntriesProvider_.parseVmSymbols(
name, startAddr, endAddr, aslrSlide, function(fName, fStart, fEnd) {
self.profile_.addStaticCode(fName, fStart, fEnd);
self.setCodeType(fName, 'CPP');
});
};
TickProcessor.prototype.processCodeCreation = function(
type, kind, timestamp, start, size, name, maybe_func) {
if (maybe_func.length) {
var funcAddr = parseInt(maybe_func[0]);
var state = parseState(maybe_func[1]);
this.profile_.addFuncCode(type, name, timestamp, start, size, funcAddr, state);
} else {
this.profile_.addCode(type, name, timestamp, start, size);
}
};
TickProcessor.prototype.processCodeDeopt = function(
timestamp, size, code, inliningId, scriptOffset, bailoutType,
sourcePositionText, deoptReasonText) {
this.profile_.deoptCode(timestamp, code, inliningId, scriptOffset,
bailoutType, sourcePositionText, deoptReasonText);
};
TickProcessor.prototype.processCodeMove = function(from, to) {
this.profile_.moveCode(from, to);
};
TickProcessor.prototype.processCodeDelete = function(start) {
this.profile_.deleteCode(start);
};
TickProcessor.prototype.processCodeSourceInfo = function(
start, script, startPos, endPos, sourcePositions, inliningPositions,
inlinedFunctions) {
this.profile_.addSourcePositions(start, script, startPos,
endPos, sourcePositions, inliningPositions, inlinedFunctions);
};
TickProcessor.prototype.processScriptSource = function(script, url, source) {
this.profile_.addScriptSource(script, url, source);
};
TickProcessor.prototype.processFunctionMove = function(from, to) {
this.profile_.moveFunc(from, to);
};
TickProcessor.prototype.includeTick = function(vmState) {
if (this.stateFilter_ !== null) {
return this.stateFilter_ == vmState;
} else if (this.runtimeTimerFilter_ !== null) {
return this.currentRuntimeTimer == this.runtimeTimerFilter_;
}
return true;
};
TickProcessor.prototype.processRuntimeTimerEvent = function(name) {
this.currentRuntimeTimer = name;
}
TickProcessor.prototype.processTick = function(pc,
ns_since_start,
is_external_callback,
tos_or_external_callback,
vmState,
stack) {
this.distortion += this.distortion_per_entry;
ns_since_start -= this.distortion;
if (ns_since_start < this.range_start || ns_since_start > this.range_end) {
return;
}
this.ticks_.total++;
if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++;
if (!this.includeTick(vmState)) {
this.ticks_.excluded++;
return;
}
if (is_external_callback) {
// Don't use PC when in external callback code, as it can point
// inside callback's code, and we will erroneously report
// that a callback calls itself. Instead we use tos_or_external_callback,
// as simply resetting PC will produce unaccounted ticks.
pc = tos_or_external_callback;
tos_or_external_callback = 0;
} else if (tos_or_external_callback) {
// Find out, if top of stack was pointing inside a JS function
// meaning that we have encountered a frameless invocation.
var funcEntry = this.profile_.findEntry(tos_or_external_callback);
if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) {
tos_or_external_callback = 0;
}
}
this.profile_.recordTick(
ns_since_start, vmState,
this.processStack(pc, tos_or_external_callback, stack));
};
TickProcessor.prototype.advanceDistortion = function() {
this.distortion += this.distortion_per_entry;
}
TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) {
if (space != 'Heap') return;
this.currentProducerProfile_ = new CallTree();
};
TickProcessor.prototype.processHeapSampleEnd = function(space, state) {
if (space != 'Heap' || !this.currentProducerProfile_) return;
print('Generation ' + this.generation_ + ':');
var tree = this.currentProducerProfile_;
tree.computeTotalWeights();
var producersView = this.viewBuilder_.buildView(tree);
// Sort by total time, desc, then by name, desc.
producersView.sort(function(rec1, rec2) {
return rec2.totalTime - rec1.totalTime ||
(rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
this.printHeavyProfile(producersView.head.children);
this.currentProducerProfile_ = null;
this.generation_++;
};
TickProcessor.prototype.printStatistics = function() {
if (this.preprocessJson) {
this.profile_.writeJson();
return;
}
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;
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;
}
var printAllTicks = !this.onlySummary_;
// Count library ticks
var flatViewNodes = flatView.head.children;
var self = this;
var libraryTicks = 0;
if(printAllTicks) this.printHeader('Shared libraries');
this.printEntries(flatViewNodes, totalTicks, null,
function(name) { return self.isSharedLibrary(name); },
function(rec) { libraryTicks += rec.selfTime; }, printAllTicks);
var nonLibraryTicks = totalTicks - libraryTicks;
var jsTicks = 0;
if(printAllTicks) this.printHeader('JavaScript');
this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
function(name) { return self.isJsCode(name); },
function(rec) { jsTicks += rec.selfTime; }, printAllTicks);
var cppTicks = 0;
if(printAllTicks) this.printHeader('C++');
this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
function(name) { return self.isCppCode(name); },
function(rec) { cppTicks += rec.selfTime; }, printAllTicks);
this.printHeader('Summary');
this.printLine('JavaScript', jsTicks, totalTicks, nonLibraryTicks);
this.printLine('C++', cppTicks, totalTicks, nonLibraryTicks);
this.printLine('GC', this.ticks_.gc, totalTicks, nonLibraryTicks);
this.printLine('Shared libraries', libraryTicks, totalTicks, null);
if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) {
this.printLine('Unaccounted', this.ticks_.unaccounted,
this.ticks_.total, null);
}
if(printAllTicks) {
print('\n [C++ entry points]:');
print(' ticks cpp total name');
var c_entry_functions = this.profile_.getCEntryProfile();
var total_c_entry = c_entry_functions[0].ticks;
for (var i = 1; i < c_entry_functions.length; i++) {
c = c_entry_functions[i];
this.printLine(c.name, c.ticks, total_c_entry, 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) {
var padLength = len - s.length;
if (!(padLength in padLeft)) {
padLeft[padLength] = new Array(padLength + 1).join(' ');
}
s = padLeft[padLength] + s;
}
return s;
};
TickProcessor.prototype.printHeader = function(headerTitle) {
print('\n [' + headerTitle + ']:');
print(' ticks total nonlib name');
};
TickProcessor.prototype.printLine = function(
entry, ticks, totalTicks, nonLibTicks) {
var pct = ticks * 100 / totalTicks;
var nonLibPct = nonLibTicks != null
? padLeft((ticks * 100 / nonLibTicks).toFixed(1), 5) + '% '
: ' ';
print(' ' + padLeft(ticks, 5) + ' ' +
padLeft(pct.toFixed(1), 5) + '% ' +
nonLibPct +
entry);
}
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.processProfile = function(
profile, filterP, func) {
for (var i = 0, n = profile.length; i < n; ++i) {
var rec = profile[i];
if (!filterP(rec.internalFuncName)) {
continue;
}
func(rec);
}
};
TickProcessor.prototype.getLineAndColumn = function(name) {
var re = /:([0-9]+):([0-9]+)$/;
var array = re.exec(name);
if (!array) {
return null;
}
return {line: array[1], column: array[2]};
}
TickProcessor.prototype.hasSourceMap = function() {
return this.sourceMap != null;
};
TickProcessor.prototype.formatFunctionName = function(funcName) {
if (!this.hasSourceMap()) {
return funcName;
}
var lc = this.getLineAndColumn(funcName);
if (lc == null) {
return funcName;
}
// in source maps lines and columns are zero based
var lineNumber = lc.line - 1;
var column = lc.column - 1;
var entry = this.sourceMap.findEntry(lineNumber, column);
var sourceFile = entry[2];
var sourceLine = entry[3] + 1;
var sourceColumn = entry[4] + 1;
return sourceFile + ':' + sourceLine + ':' + sourceColumn + ' -> ' + funcName;
};
TickProcessor.prototype.printEntries = function(
profile, totalTicks, nonLibTicks, filterP, callback, printAllTicks) {
var that = this;
this.processProfile(profile, filterP, function (rec) {
if (rec.selfTime == 0) return;
callback(rec);
var funcName = that.formatFunctionName(rec.internalFuncName);
if(printAllTicks) {
that.printLine(funcName, rec.selfTime, totalTicks, nonLibTicks);
}
});
};
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;
var funcName = self.formatFunctionName(rec.internalFuncName);
print(' ' + padLeft(rec.totalTime, 5) + ' ' +
padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' +
indentStr + funcName);
// Limit backtrace depth.
if (indent < 2 * self.callGraphSize_) {
self.printHeavyProfile(rec.children, indent + 2);
}
// Delimit top-level functions.
if (indent == 0) {
print('');
}
});
};
function CppEntriesProvider() {
};
CppEntriesProvider.prototype.parseVmSymbols = function(
libName, libStart, libEnd, libASLRSlide, processorFunc) {
this.loadSymbols(libName);
var lastUnknownSize;
var lastAdded;
function inRange(funcInfo, start, end) {
return funcInfo.start >= start && funcInfo.end <= end;
}
function addEntry(funcInfo) {
// Several functions can be mapped onto the same address. To avoid
// creating zero-sized entries, skip such duplicates.
// Also double-check that function belongs to the library address space.
if (lastUnknownSize &&
lastUnknownSize.start < funcInfo.start) {
// Try to update lastUnknownSize based on new entries start position.
lastUnknownSize.end = funcInfo.start;
if ((!lastAdded || !inRange(lastUnknownSize, lastAdded.start,
lastAdded.end)) &&
inRange(lastUnknownSize, libStart, libEnd)) {
processorFunc(lastUnknownSize.name, lastUnknownSize.start,
lastUnknownSize.end);
lastAdded = lastUnknownSize;
}
}
lastUnknownSize = undefined;
if (funcInfo.end) {
// Skip duplicates that have the same start address as the last added.
if ((!lastAdded || lastAdded.start != funcInfo.start) &&
inRange(funcInfo, libStart, libEnd)) {
processorFunc(funcInfo.name, funcInfo.start, funcInfo.end);
lastAdded = funcInfo;
}
} else {
// If a funcInfo doesn't have an end, try to match it up with then next
// entry.
lastUnknownSize = funcInfo;
}
}
while (true) {
var funcInfo = this.parseNextLine();
if (funcInfo === null) {
continue;
} else if (funcInfo === false) {
break;
}
if (funcInfo.start < libStart - libASLRSlide &&
funcInfo.start < libEnd - libStart) {
funcInfo.start += libStart;
} else {
funcInfo.start += libASLRSlide;
}
if (funcInfo.size) {
funcInfo.end = funcInfo.start + funcInfo.size;
}
addEntry(funcInfo);
}
addEntry({name: '', start: libEnd});
};
CppEntriesProvider.prototype.loadSymbols = function(libName) {
};
CppEntriesProvider.prototype.parseNextLine = function() {
return false;
};
function UnixCppEntriesProvider(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) {
this.symbols = [];
// File offset of a symbol minus the virtual address of a symbol found in
// the symbol table.
this.fileOffsetMinusVma = 0;
this.parsePos = 0;
this.nmExec = nmExec;
this.objdumpExec = objdumpExec;
this.targetRootFS = targetRootFS;
this.apkEmbeddedLibrary = apkEmbeddedLibrary;
this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/;
};
inherits(UnixCppEntriesProvider, CppEntriesProvider);
UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
this.parsePos = 0;
if (this.apkEmbeddedLibrary && libName.endsWith('.apk')) {
libName = this.apkEmbeddedLibrary;
}
if (this.targetRootFS) {
libName = libName.substring(libName.lastIndexOf('/') + 1);
libName = this.targetRootFS + libName;
}
try {
this.symbols = [
os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1),
os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1)
];
const objdumpOutput = os.system(this.objdumpExec, ['-h', libName], -1, -1);
for (const line of objdumpOutput.split('\n')) {
const [,sectionName,,vma,,fileOffset] = line.trim().split(/\s+/);
if (sectionName === ".text") {
this.fileOffsetMinusVma = parseInt(fileOffset, 16) - parseInt(vma, 16);
}
}
} catch (e) {
// If the library cannot be found on this system let's not panic.
this.symbols = ['', ''];
}
};
UnixCppEntriesProvider.prototype.parseNextLine = function() {
if (this.symbols.length == 0) {
return false;
}
var lineEndPos = this.symbols[0].indexOf('\n', this.parsePos);
if (lineEndPos == -1) {
this.symbols.shift();
this.parsePos = 0;
return this.parseNextLine();
}
var line = this.symbols[0].substring(this.parsePos, lineEndPos);
this.parsePos = lineEndPos + 1;
var fields = line.match(this.FUNC_RE);
var funcInfo = null;
if (fields) {
funcInfo = { name: fields[3], start: parseInt(fields[1], 16) + this.fileOffsetMinusVma };
if (fields[2]) {
funcInfo.size = parseInt(fields[2], 16);
}
}
return funcInfo;
};
function MacCppEntriesProvider(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) {
UnixCppEntriesProvider.call(this, nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary);
// Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups.
this.FUNC_RE = /^([0-9a-fA-F]{8,16})() (.*)$/;
};
inherits(MacCppEntriesProvider, UnixCppEntriesProvider);
MacCppEntriesProvider.prototype.loadSymbols = function(libName) {
this.parsePos = 0;
libName = this.targetRootFS + libName;
// It seems that in OS X `nm` thinks that `-f` is a format option, not a
// "flat" display option flag.
try {
this.symbols = [os.system(this.nmExec, ['-n', libName], -1, -1), ''];
} catch (e) {
// If the library cannot be found on this system let's not panic.
this.symbols = '';
}
};
function WindowsCppEntriesProvider(_ignored_nmExec, _ignored_objdumpExec, targetRootFS,
_ignored_apkEmbeddedLibrary) {
this.targetRootFS = targetRootFS;
this.symbols = '';
this.parsePos = 0;
};
inherits(WindowsCppEntriesProvider, CppEntriesProvider);
WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/;
WindowsCppEntriesProvider.FUNC_RE =
/^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/;
WindowsCppEntriesProvider.IMAGE_BASE_RE =
/^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/;
// This is almost a constant on Windows.
WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000;
WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) {
libName = this.targetRootFS + libName;
var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE);
if (!fileNameFields) return;
var mapFileName = fileNameFields[1] + '.map';
this.moduleType_ = fileNameFields[2].toLowerCase();
try {
this.symbols = read(mapFileName);
} catch (e) {
// If .map file cannot be found let's not panic.
this.symbols = '';
}
};
WindowsCppEntriesProvider.prototype.parseNextLine = function() {
var lineEndPos = this.symbols.indexOf('\r\n', this.parsePos);
if (lineEndPos == -1) {
return false;
}
var line = this.symbols.substring(this.parsePos, lineEndPos);
this.parsePos = lineEndPos + 2;
// Image base entry is above all other symbols, so we can just
// terminate parsing.
var imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE);
if (imageBaseFields) {
var imageBase = parseInt(imageBaseFields[1], 16);
if ((this.moduleType_ == 'exe') !=
(imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) {
return false;
}
}
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('::');
};
class ArgumentsProcessor extends BaseArgumentsProcessor {
getArgsDispatch() {
let dispatch = {
'-j': ['stateFilter', TickProcessor.VmStates.JS,
'Show only ticks from JS VM state'],
'-g': ['stateFilter', TickProcessor.VmStates.GC,
'Show only ticks from GC VM state'],
'-p': ['stateFilter', TickProcessor.VmStates.PARSER,
'Show only ticks from PARSER VM state'],
'-b': ['stateFilter', TickProcessor.VmStates.BYTECODE_COMPILER,
'Show only ticks from BYTECODE_COMPILER 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'],
'--filter-runtime-timer': ['runtimeTimerFilter', null,
'Show only ticks matching the given runtime timer scope'],
'--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE,
'Set the call graph size'],
'--ignore-unknown': ['ignoreUnknown', true,
'Exclude ticks of unknown code entries from processing'],
'--separate-ic': ['separateIc', parseBool,
'Separate IC entries'],
'--separate-bytecodes': ['separateBytecodes', parseBool,
'Separate Bytecode entries'],
'--separate-builtins': ['separateBuiltins', parseBool,
'Separate Builtin entries'],
'--separate-stubs': ['separateStubs', parseBool,
'Separate Stub entries'],
'--unix': ['platform', 'unix',
'Specify that we are running on *nix platform'],
'--windows': ['platform', 'windows',
'Specify that we are running on Windows platform'],
'--mac': ['platform', 'mac',
'Specify that we are running on Mac OS X platform'],
'--nm': ['nm', 'nm',
'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'],
'--objdump': ['objdump', 'objdump',
'Specify the \'objdump\' executable to use (e.g. --objdump=/my_dir/objdump)'],
'--target': ['targetRootFS', '',
'Specify the target root directory for cross environment'],
'--apk-embedded-library': ['apkEmbeddedLibrary', '',
'Specify the path of the embedded library for Android traces'],
'--range': ['range', 'auto,auto',
'Specify the range limit as [start],[end]'],
'--distortion': ['distortion', 0,
'Specify the logging overhead in picoseconds'],
'--source-map': ['sourceMap', null,
'Specify the source map that should be used for output'],
'--timed-range': ['timedRange', true,
'Ignore ticks before first and after last Date.now() call'],
'--pairwise-timed-range': ['pairwiseTimedRange', true,
'Ignore ticks outside pairs of Date.now() calls'],
'--only-summary': ['onlySummary', true,
'Print only tick summary, exclude other information'],
'--preprocess': ['preprocessJson', true,
'Preprocess for consumption with web interface']
};
dispatch['--js'] = dispatch['-j'];
dispatch['--gc'] = dispatch['-g'];
dispatch['--compiler'] = dispatch['-c'];
dispatch['--other'] = dispatch['-o'];
dispatch['--external'] = dispatch['-e'];
dispatch['--ptr'] = dispatch['--pairwise-timed-range'];
return dispatch;
}
getDefaultResults() {
return {
logFileName: 'v8.log',
platform: 'unix',
stateFilter: null,
callGraphSize: 5,
ignoreUnknown: false,
separateIc: true,
separateBytecodes: false,
separateBuiltins: true,
separateStubs: true,
preprocessJson: null,
targetRootFS: '',
nm: 'nm',
objdump: 'objdump',
range: 'auto,auto',
distortion: 0,
timedRange: false,
pairwiseTimedRange: false,
onlySummary: false,
runtimeTimerFilter: null,
};
}
}