Commit fcce4797 authored by jarin's avatar jarin Committed by Commit bot

[profiler] Graphical front-end for tick processor.

Improvements:
- top-down call tree.
- interactive restriction to time interval.

Review-Url: https://codereview.chromium.org/2696903002
Cr-Commit-Position: refs/heads/master@{#43599}
parent 885ec933
......@@ -88,12 +88,12 @@ 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_);
this.profile.recordTick(0, 0, this.stack_);
};
ProfileTestDriver.prototype.stay = function() {
this.profile.recordTick(this.stack_);
this.profile.recordTick(0, 0, this.stack_);
};
......
......@@ -131,15 +131,14 @@
// shell executable
MacCppEntriesProvider.prototype.loadSymbols = function(libName) {
this.symbols = [[
' U operator delete[]',
'00001000 A __mh_execute_header',
'00001b00 T start',
'00001b40 t dyld_stub_binding_helper',
'0011b710 T v8::internal::RegExpMacroAssembler::CheckPosition',
'00134250 t v8::internal::Runtime_StringReplaceRegExpWithString',
'00137220 T v8::internal::Runtime::GetElementOrCharAt',
'00137400 t v8::internal::Runtime_DebugGetPropertyDetails',
'001c1a80 b _private_mem\n'
' operator delete[]',
'00001000 __mh_execute_header',
'00001b00 start',
'00001b40 dyld_stub_binding_helper',
'0011b710 v8::internal::RegExpMacroAssembler::CheckPosition',
'00134250 v8::internal::Runtime_StringReplaceRegExpWithString',
'00137220 v8::internal::Runtime::GetElementOrCharAt',
'00137400 v8::internal::Runtime_DebugGetPropertyDetails\n'
].join('\n'), ''];
};
......@@ -161,10 +160,10 @@
// stdc++ library
MacCppEntriesProvider.prototype.loadSymbols = function(libName) {
this.symbols = [[
'0000107a T __gnu_cxx::balloc::__mini_vector<std::pair<__gnu_cxx::bitmap_allocator<char>::_Alloc_block*, __gnu_cxx::bitmap_allocator<char>::_Alloc_block*> >::__mini_vector',
'0002c410 T std::basic_streambuf<char, std::char_traits<char> >::pubseekoff',
'0002c488 T std::basic_streambuf<char, std::char_traits<char> >::pubseekpos',
'000466aa T ___cxa_pure_virtual\n'].join('\n'), ''];
'0000107a __gnu_cxx::balloc::__mini_vector<std::pair<__gnu_cxx::bitmap_allocator<char>::_Alloc_block*, __gnu_cxx::bitmap_allocator<char>::_Alloc_block*> >::__mini_vector',
'0002c410 std::basic_streambuf<char, std::char_traits<char> >::pubseekoff',
'0002c488 std::basic_streambuf<char, std::char_traits<char> >::pubseekpos',
'000466aa ___cxa_pure_virtual\n'].join('\n'), ''];
};
var stdc_prov = new MacCppEntriesProvider();
var stdc_syms = [];
......
......@@ -175,23 +175,28 @@ CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) {
*/
CodeMap.prototype.findInTree_ = function(tree, addr) {
var node = tree.findGreatestLessThan(addr);
return node && this.isAddressBelongsTo_(addr, node) ? node.value : null;
return node && this.isAddressBelongsTo_(addr, node) ? node : null;
};
/**
* Finds a code entry that contains the specified address. Both static and
* dynamic code entries are considered.
* dynamic code entries are considered. Returns the code entry and the offset
* within the entry.
*
* @param {number} addr Address.
*/
CodeMap.prototype.findEntry = function(addr) {
CodeMap.prototype.findAddress = function(addr) {
var pageAddr = addr >>> CodeMap.PAGE_ALIGNMENT;
if (pageAddr in this.pages_) {
// Static code entries can contain "holes" of unnamed code.
// In this case, the whole library is assigned to this address.
return this.findInTree_(this.statics_, addr) ||
this.findInTree_(this.libraries_, addr);
var result = this.findInTree_(this.statics_, addr);
if (!result) {
result = this.findInTree_(this.libraries_, addr);
if (!result) return null;
}
return { entry : result.value, offset : addr - result.key };
}
var min = this.dynamics_.findMin();
var max = this.dynamics_.findMax();
......@@ -199,16 +204,29 @@ CodeMap.prototype.findEntry = function(addr) {
var dynaEntry = this.findInTree_(this.dynamics_, addr);
if (dynaEntry == null) return null;
// Dedupe entry name.
if (!dynaEntry.nameUpdated_) {
dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name);
dynaEntry.nameUpdated_ = true;
var entry = dynaEntry.value;
if (!entry.nameUpdated_) {
entry.name = this.dynamicsNameGen_.getName(entry.name);
entry.nameUpdated_ = true;
}
return dynaEntry;
return { entry : entry, offset : addr - dynaEntry.key };
}
return null;
};
/**
* Finds a code entry that contains the specified address. Both static and
* dynamic code entries are considered.
*
* @param {number} addr Address.
*/
CodeMap.prototype.findEntry = function(addr) {
var result = this.findAddress(addr);
return result ? result.entry : null;
};
/**
* Returns a dynamic code entry using its starting address.
*
......
......@@ -14,5 +14,6 @@
if [ "`which c++filt`" == "" ]; then
nm "$@"
else
nm "$@" | c++filt -p -i
nm "$@" | sed -n "s/\([0-9a-fA-F]\{8,16\}\) [iItT] \(.*\)/\\1 \\2/p"\
| c++filt -p -i
fi
......@@ -37,6 +37,7 @@ function Profile() {
this.topDownTree_ = new CallTree();
this.bottomUpTree_ = new CallTree();
this.c_entries_ = {};
this.ticks_ = [];
};
......@@ -235,7 +236,7 @@ Profile.prototype.findEntry = function(addr) {
*
* @param {Array<number>} stack Stack sample.
*/
Profile.prototype.recordTick = function(stack) {
Profile.prototype.recordTick = function(time_ns, vmState, stack) {
var processedStack = this.resolveAndFilterFuncs_(stack);
this.bottomUpTree_.addPath(processedStack);
processedStack.reverse();
......@@ -832,3 +833,146 @@ CallTree.Node.prototype.descendToChild = function(
}
return curr;
};
function JsonProfile() {
this.codeMap_ = new CodeMap();
this.codeEntries_ = [];
this.functionEntries_ = [];
this.ticks_ = [];
}
JsonProfile.prototype.addLibrary = function(
name, startAddr, endAddr) {
var entry = new CodeMap.CodeEntry(
endAddr - startAddr, name, 'SHARED_LIB');
this.codeMap_.addLibrary(startAddr, entry);
entry.codeId = this.codeEntries_.length;
this.codeEntries_.push({name : entry.name, type : entry.type});
return entry;
};
JsonProfile.prototype.addStaticCode = function(
name, startAddr, endAddr) {
var entry = new CodeMap.CodeEntry(
endAddr - startAddr, name, 'CPP');
this.codeMap_.addStaticCode(startAddr, entry);
entry.codeId = this.codeEntries_.length;
this.codeEntries_.push({name : entry.name, type : entry.type});
return entry;
};
JsonProfile.prototype.addCode = function(
kind, name, start, size) {
var entry = new CodeMap.CodeEntry(size, name, 'CODE');
this.codeMap_.addCode(start, entry);
entry.codeId = this.codeEntries_.length;
this.codeEntries_.push({name : entry.name, type : entry.type, kind : kind});
return entry;
};
JsonProfile.prototype.addFuncCode = function(
kind, name, start, size, funcAddr, state) {
// As code and functions are in the same address space,
// it is safe to put them in a single code map.
var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
if (!func) {
var func = new CodeMap.CodeEntry(0, name, 'SFI');
this.codeMap_.addCode(funcAddr, func);
func.funcId = this.functionEntries_.length;
this.functionEntries_.push({name : name, codes : []});
} else if (func.name !== name) {
// Function object has been overwritten with a new one.
func.name = name;
func.funcId = this.functionEntries_.length;
this.functionEntries_.push({name : name, codes : []});
}
// TODO(jarin): Insert the code object into the SFI's code list.
var entry = this.codeMap_.findDynamicEntryByStartAddress(start);
if (entry) {
// TODO(jarin) This does not look correct, we should really
// update the code object (remove the old one and insert this one).
if (entry.size === size && entry.func === func) {
// Entry state has changed.
entry.state = state;
}
} else {
var entry = new CodeMap.CodeEntry(size, name, 'JS');
this.codeMap_.addCode(start, entry);
entry.codeId = this.codeEntries_.length;
this.functionEntries_[func.funcId].codes.push(entry.codeId);
if (state === 0) {
kind = "Builtin";
} else if (state === 1) {
kind = "Unopt";
} else if (state === 2) {
kind = "Opt";
}
this.codeEntries_.push({
name : entry.name,
type : entry.type,
kind : kind,
func : func.funcId
});
}
return entry;
};
JsonProfile.prototype.moveCode = function(from, to) {
try {
this.codeMap_.moveCode(from, to);
} catch (e) {
printErr("Move: unknown source " + from);
}
};
JsonProfile.prototype.deleteCode = function(start) {
try {
this.codeMap_.deleteCode(start);
} catch (e) {
printErr("Delete: unknown address " + start);
}
};
JsonProfile.prototype.moveFunc = function(from, to) {
if (this.codeMap_.findDynamicEntryByStartAddress(from)) {
this.codeMap_.moveCode(from, to);
}
};
JsonProfile.prototype.findEntry = function(addr) {
return this.codeMap_.findEntry(addr);
};
JsonProfile.prototype.recordTick = function(time_ns, vmState, stack) {
// TODO(jarin) Resolve the frame-less case (when top of stack is
// known code).
var processedStack = [];
for (var i = 0; i < stack.length; i++) {
var resolved = this.codeMap_.findAddress(stack[i]);
if (resolved) {
processedStack.push(resolved.entry.codeId, resolved.offset);
} else {
processedStack.push(-1, stack[i]);
}
}
this.ticks_.push({ tm : time_ns, vm : vmState, s : processedStack });
};
JsonProfile.prototype.writeJson = function() {
var toplevel = {
code : this.codeEntries_,
functions : this.functionEntries_,
ticks : this.ticks_
};
write(JSON.stringify(toplevel));
};
<!DOCTYPE html>
<!-- Copyright 2017 the V8 project authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>V8 Tick Processor</title>
<link rel="stylesheet" href="profview.css">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<script src="profview.js"></script>
<script src="profile-utils.js"></script>
</head>
<body onLoad="main.onLoad();" onResize="main.onResize();">
<h3 style="margin-top: 2px;">
Chrome V8 profiling log processor
</h3>
<input type="file" id="fileinput" />
<br>
<hr>
<div id="help">
Usage:
<br>
<br>
Record the profile:
<pre>
d8 --prof your-file.js
</pre>
Then process the file (this resolves C++ symbols and produces
a JSON file with the profile data):
<pre>
&lt;v8-dir>/tools/linux-tick-processor --preprocess v8.log > v8.json
</pre>
To view the profile, click the <i>Choose file</i> button above and choose
the file in the dialog box.
</div>
<div id="timeline" style="display : none">
<div>
<canvas id="timeline-canvas"/>
</div>
<table>
<tr id="timeline-legend">
</tr>
</table>
</div>
<br>
<div id="calltree" style="display : none">
<div id="mode-bar">
</div>
<br>
Attribution:
<select id="calltree-attribution">
</select>
Top-level tree buckets:
<select id="calltree-categories">
</select>
Sort by:
<select id="calltree-sort">
</select>
<br>
<br>
<table id="calltree-table" class="calltree">
<thead>
<tr>
<th class="numeric">Time (incl)</th>
<th class="numeric">% of parent</th>
<th id="calltree-table-own-time-header" class="numeric">Own time</th>
<th>Function/category</th>
<th class="numeric">Ticks</th>
<th id="calltree-table-own-ticks-header" class="numeric">Own ticks</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<p style="font-style:italic;">
<br>
<br>
<br>
Copyright the V8 Authors - Last change to this page: 2017/02/15
</p>
</body>
</html>
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict"
let codeKinds = [
"UNKNOWN",
"CPPCOMP",
"CPPGC",
"CPPEXT",
"CPP",
"LIB",
"IC",
"BC",
"STUB",
"BUILTIN",
"REGEXP",
"JSOPT",
"JSUNOPT"
];
function resolveCodeKind(code) {
if (!code || !code.type) {
return "UNKNOWN";
} else if (code.type === "CPP") {
return "CPP";
} else if (code.type === "SHARED_LIB") {
return "LIB";
} else if (code.type === "CODE") {
if (code.kind === "LoadIC" ||
code.kind === "StoreIC" ||
code.kind === "KeyedStoreIC" ||
code.kind === "KeyedLoadIC" ||
code.kind === "LoadGlobalIC" ||
code.kind === "Handler") {
return "IC";
} else if (code.kind === "BytecodeHandler") {
return "BC";
} else if (code.kind === "Stub") {
return "STUB";
} else if (code.kind === "Builtin") {
return "BUILTIN";
} else if (code.kind === "RegExp") {
return "REGEXP";
}
console.log("Unknown CODE: '" + code.kind + "'.");
return "CODE";
} else if (code.type === "JS") {
if (code.kind === "Builtin") {
return "JSUNOPT";
} else if (code.kind === "Opt") {
return "JSOPT";
} else if (code.kind === "Unopt") {
return "JSUNOPT";
}
}
console.log("Unknown code type '" + type + "'.");
}
function resolveCodeKindAndVmState(code, vmState) {
let kind = resolveCodeKind(code);
if (kind === "CPP") {
if (vmState === 1) {
kind = "CPPGC";
} else if (vmState === 2) {
kind = "CPPCOMP";
} else if (vmState === 4) {
kind = "CPPEXT";
}
}
return kind;
}
function createNodeFromStackEntry(code) {
let name = code ? code.name : "UNKNOWN";
return { name, type : resolveCodeKind(code),
children : [], ownTicks : 0, ticks : 0 };
}
function addStackToTree(file, stack, tree, filter, ascending, start) {
if (start === undefined) {
start = ascending ? 0 : stack.length - 2;
}
tree.ticks++;
for (let i = start;
ascending ? (i < stack.length) : (i >= 0);
i += ascending ? 2 : -2) {
let codeId = stack[i];
let code = codeId >= 0 ? file.code[codeId] : undefined;
if (filter) {
let type = code ? code.type : undefined;
let kind = code ? code.kind : undefined;
if (!filter(type, kind)) continue;
}
// For JavaScript function, pretend there is one instance of optimized
// function and one instance of unoptimized function per SFI.
let type = resolveCodeKind(code);
let childId;
if (type === "JSOPT") {
childId = code.func * 4 + 1;
} else if (type === "JSUNOPT") {
childId = code.func * 4 + 2;
} else {
childId = codeId * 4;
}
let child = tree.children[childId];
if (!child) {
child = createNodeFromStackEntry(code);
tree.children[childId] = child;
}
child.ticks++;
tree = child;
}
tree.ownTicks++;
}
function createEmptyNode(name) {
return {
name : name,
type : "CAT",
children : [],
ownTicks : 0,
ticks : 0
};
}
class PlainCallTreeProcessor {
constructor(filter, isBottomUp) {
this.filter = filter;
this.tree = createEmptyNode("root");
this.isBottomUp = isBottomUp;
}
addStack(file, timestamp, vmState, stack) {
addStackToTree(file, stack, this.tree, this.filter, this.isBottomUp);
}
}
class CategorizedCallTreeProcessor {
constructor(filter, isBottomUp) {
this.filter = filter;
let root = createEmptyNode("root");
let categories = {};
function addCategory(name, types) {
let n = createEmptyNode(name);
for (let i = 0; i < types.length; i++) {
categories[types[i]] = n;
}
root.children.push(n);
}
addCategory("JS Optimized", [ "JSOPT" ]);
addCategory("JS Unoptimized", [ "JSUNOPT", "BC" ]);
addCategory("IC", [ "IC" ]);
addCategory("RegExp", [ "REGEXP" ]);
addCategory("Other generated", [ "STUB", "BUILTIN" ]);
addCategory("C++", [ "CPP", "LIB" ]);
addCategory("C++/GC", [ "CPPGC" ]);
addCategory("C++/Compiler", [ "CPPCOMP" ]);
addCategory("C++/External", [ "CPPEXT" ]);
addCategory("Unknown", [ "UNKNOWN" ]);
this.tree = root;
this.categories = categories;
this.isBottomUp = isBottomUp;
}
addStack(file, timestamp, vmState, stack) {
if (stack.length === 0) return;
let codeId = stack[0];
let code = codeId >= 0 ? file.code[codeId] : undefined;
let kind = resolveCodeKindAndVmState(code, vmState);
let node = this.categories[kind];
this.tree.ticks++;
console.assert(node);
addStackToTree(file, stack, node, this.filter, this.isBottomUp);
}
}
class FunctionListTree {
constructor(filter) {
this.tree = { name : "root", children : [], ownTicks : 0, ticks : 0 };
this.codeVisited = [];
this.filter = filter;
}
addStack(file, timestamp, vmState, stack) {
this.tree.ticks++;
let child = null;
for (let i = stack.length - 2; i >= 0; i -= 2) {
let codeId = stack[i];
if (codeId < 0 || this.codeVisited[codeId]) continue;
let code = codeId >= 0 ? file.code[codeId] : undefined;
if (this.filter) {
let type = code ? code.type : undefined;
let kind = code ? code.kind : undefined;
if (!this.filter(type, kind)) continue;
}
child = this.tree.children[codeId];
if (!child) {
child = createNodeFromStackEntry(code);
this.tree.children[codeId] = child;
}
child.ticks++;
this.codeVisited[codeId] = true;
}
if (child) {
child.ownTicks++;
}
for (let i = 0; i < stack.length; i += 2) {
let codeId = stack[i];
if (codeId >= 0) this.codeVisited[codeId] = false;
}
}
}
class CategorySampler {
constructor(file, bucketCount) {
this.bucketCount = bucketCount;
this.firstTime = file.ticks[0].tm;
let lastTime = file.ticks[file.ticks.length - 1].tm;
this.step = (lastTime - this.firstTime) / bucketCount;
this.buckets = [];
let bucket = {};
for (let i = 0; i < codeKinds.length; i++) {
bucket[codeKinds[i]] = 0;
}
for (let i = 0; i < bucketCount; i++) {
this.buckets.push(Object.assign({ total : 0 }, bucket));
}
}
addStack(file, timestamp, vmState, stack) {
let i = Math.floor((timestamp - this.firstTime) / this.step);
if (i == this.buckets.length) i--;
console.assert(i >= 0 && i < this.buckets.length);
let bucket = this.buckets[i];
bucket.total++;
let codeId = (stack.length > 0) ? stack[0] : -1;
let code = codeId >= 0 ? file.code[codeId] : undefined;
let kind = resolveCodeKindAndVmState(code, vmState);
bucket[kind]++;
}
}
// Generates a tree out of a ticks sequence.
// {file} is the JSON files with the ticks and code objects.
// {startTime}, {endTime} is the interval.
// {tree} is the processor of stacks.
function generateTree(
file, startTime, endTime, tree) {
let ticks = file.ticks;
let i = 0;
while (i < ticks.length && ticks[i].tm < startTime) {
i++;
}
let tickCount = 0;
while (i < ticks.length && ticks[i].tm < endTime) {
tree.addStack(file, ticks[i].tm, ticks[i].vm, ticks[i].s);
i++;
tickCount++;
}
return tickCount;
}
table.calltree {
width : 100%;
}
.numeric {
width : 12ex;
}
body {
font-family: 'Roboto', sans-serif;
}
div.code-type-chip {
display : inline-block;
padding : 0.0em;
}
span.code-type-chip {
border-radius : 1em;
display : inline-block;
padding : 0.1em;
background-color : #4040c0;
color: #ffffff;
font-size : small;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
span.code-type-chip-space {
width : 0.5ex;
display : inline-block;
}
div.mode-button {
padding: 1em 3em;
display: inline-block;
background-color: #6070ff;
color : #ffffff;
margin: 0 0.2em 2em 0;
box-shadow: 3px 3px 2px #d0d0ff;
}
div.mode-button:hover {
background-color: #4858ff;
}
div.active-mode-button {
background-color: #0000ff;
box-shadow: 3px 3px 2px #a0a0ff;
}
div.active-mode-button:hover {
background-color: #0000ff;
}
This diff is collapsed.
......@@ -73,6 +73,7 @@ var tickProcessor = new TickProcessor(
params.timedRange,
params.pairwiseTimedRange,
params.onlySummary,
params.runtimeTimerFilter);
params.runtimeTimerFilter,
params.preprocessJson);
tickProcessor.processLogFile(params.logFileName);
tickProcessor.printStatistics();
......@@ -50,7 +50,7 @@ function readFile(fileName) {
try {
return read(fileName);
} catch (e) {
print(fileName + ': ' + (e.message || e));
printErr(fileName + ': ' + (e.message || e));
throw e;
}
}
......@@ -81,7 +81,9 @@ function TickProcessor(
timedRange,
pairwiseTimedRange,
onlySummary,
runtimeTimerFilter) {
runtimeTimerFilter,
preprocessJson) {
this.preprocessJson = preprocessJson;
LogReader.call(this, {
'shared-library': { parsers: [null, parseInt, parseInt, parseInt],
processor: this.processSharedLibrary },
......@@ -149,10 +151,10 @@ function TickProcessor(
var op = Profile.Operation;
switch (operation) {
case op.MOVE:
print('Code move event for unknown code: 0x' + addr.toString(16));
printErr('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));
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,
......@@ -165,7 +167,11 @@ function TickProcessor(
}
};
this.profile_ = new V8Profile(separateIc);
if (preprocessJson) {
this.profile_ = new JsonProfile();
} else {
this.profile_ = new V8Profile(separateIc);
}
this.codeTypes_ = {};
// Count each tick as a time unit.
this.viewBuilder_ = new ViewBuilder(1);
......@@ -204,7 +210,7 @@ TickProcessor.CALL_GRAPH_SIZE = 5;
* @override
*/
TickProcessor.prototype.printError = function(str) {
print(str);
printErr(str);
};
......@@ -333,7 +339,9 @@ TickProcessor.prototype.processTick = function(pc,
}
}
this.profile_.recordTick(this.processStack(pc, tos_or_external_callback, stack));
this.profile_.recordTick(
ns_since_start, vmState,
this.processStack(pc, tos_or_external_callback, stack));
};
......@@ -367,6 +375,11 @@ TickProcessor.prototype.processHeapSampleEnd = function(space, state) {
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, ' +
......@@ -676,7 +689,7 @@ UnixCppEntriesProvider.prototype.parseNextLine = function() {
function MacCppEntriesProvider(nmExec, targetRootFS) {
UnixCppEntriesProvider.call(this, nmExec, targetRootFS);
// Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups.
this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ()[iItT] (.*)$/;
this.FUNC_RE = /^([0-9a-fA-F]{8,16})() (.*)$/;
};
inherits(MacCppEntriesProvider, UnixCppEntriesProvider);
......@@ -823,7 +836,9 @@ function ArgumentsProcessor(args) {
'--pairwise-timed-range': ['pairwiseTimedRange', true,
'Ignore ticks outside pairs of Date.now() calls'],
'--only-summary': ['onlySummary', true,
'Print only tick summary, exclude other information']
'Print only tick summary, exclude other information'],
'--preprocess': ['preprocessJson', true,
'Preprocess for consumption with web interface']
};
this.argsDispatch_['--js'] = this.argsDispatch_['-j'];
this.argsDispatch_['--gc'] = this.argsDispatch_['-g'];
......@@ -841,6 +856,7 @@ ArgumentsProcessor.DEFAULTS = {
callGraphSize: 5,
ignoreUnknown: false,
separateIc: false,
preprocessJson: null,
targetRootFS: '',
nm: 'nm',
range: 'auto,auto',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment