blob: 9321ca2d92f846a6a76c577b11222c81b1e8c475 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/tracing/base/color_scheme.html">
<link rel="import" href="/tracing/extras/importer/v8/codemap.html">
<link rel="import" href="/tracing/extras/importer/v8/log_reader.html">
<link rel="import" href="/tracing/importer/importer.html">
<link rel="import" href="/tracing/model/model.html">
<link rel="import" href="/tracing/model/slice.html">
<script>
'use strict';
/**
* @fileoverview V8LogImporter imports v8.log files into the provided model.
*/
tr.exportTo('tr.e.importer.v8', function() {
var CodeEntry = tr.e.importer.v8.CodeMap.CodeEntry;
var CodeMap = tr.e.importer.v8.CodeMap;
var ColorScheme = tr.b.ColorScheme;
var DynamicFuncCodeEntry = tr.e.importer.v8.CodeMap.DynamicFuncCodeEntry;
var FunctionEntry = tr.e.importer.v8.CodeMap.FunctionEntry;
function V8LogImporter(model, eventData) {
this.importPriority = 3;
this.model_ = model;
this.logData_ = eventData;
this.code_map_ = new CodeMap();
this.v8_timer_thread_ = undefined;
this.v8_thread_ = undefined;
this.root_stack_frame_ = new tr.model.StackFrame(
undefined, 'v8-root-stack-frame', 'v8-root-stack-frame', 0);
// We reconstruct the stack timeline from ticks.
this.v8_stack_timeline_ = new Array();
}
var kV8BinarySuffixes = ['/d8', '/libv8.so'];
var TimerEventDefaultArgs = {
'V8.Execute': { pause: false, no_execution: false},
'V8.External': { pause: false, no_execution: true},
'V8.CompileFullCode': { pause: true, no_execution: true},
'V8.RecompileSynchronous': { pause: true, no_execution: true},
'V8.RecompileParallel': { pause: false, no_execution: false},
'V8.CompileEval': { pause: true, no_execution: true},
'V8.Parse': { pause: true, no_execution: true},
'V8.PreParse': { pause: true, no_execution: true},
'V8.ParseLazy': { pause: true, no_execution: true},
'V8.GCScavenger': { pause: true, no_execution: true},
'V8.GCCompactor': { pause: true, no_execution: true},
'V8.GCContext': { pause: true, no_execution: true}
};
/**
* @return {boolean} Whether obj is a V8 log string.
*/
V8LogImporter.canImport = function(eventData) {
if (typeof(eventData) !== 'string' && !(eventData instanceof String))
return false;
return eventData.substring(0, 11) == 'v8-version,' ||
eventData.substring(0, 12) == 'timer-event,' ||
eventData.substring(0, 5) == 'tick,' ||
eventData.substring(0, 15) == 'shared-library,' ||
eventData.substring(0, 9) == 'profiler,' ||
eventData.substring(0, 14) == 'code-creation,';
};
V8LogImporter.prototype = {
__proto__: tr.importer.Importer.prototype,
get importerName() {
return 'V8LogImporter';
},
processTimerEvent_: function(name, startInUs, lengthInUs) {
var args = TimerEventDefaultArgs[name];
if (args === undefined) return;
var startInMs = tr.b.convertUnit(startInUs,
tr.b.UnitScale.Metric.MICRO, tr.b.UnitScale.Metric.MILLI);
var lengthInMs = tr.b.convertUnit(lengthInUs,
tr.b.UnitScale.Metric.MICRO, tr.b.UnitScale.Metric.MILLI);
var colorId = ColorScheme.getColorIdForGeneralPurposeString(name);
var slice = new tr.model.ThreadSlice('v8', name, colorId, startInMs,
args, lengthInMs);
this.v8_timer_thread_.sliceGroup.pushSlice(slice);
},
processTimerEventStart_: function(name, startInUs) {
var args = TimerEventDefaultArgs[name];
if (args === undefined) return;
var startInMs = tr.b.convertUnit(startInUs,
tr.b.UnitScale.Metric.MICRO, tr.b.UnitScale.Metric.MILLI);
this.v8_timer_thread_.sliceGroup.beginSlice('v8', name, startInMs, args);
},
processTimerEventEnd_: function(name, endInUs) {
var endInMs = tr.b.convertUnit(endInUs,
tr.b.UnitScale.Metric.MICRO, tr.b.UnitScale.Metric.MILLI);
this.v8_timer_thread_.sliceGroup.endSlice(endInMs);
},
processCodeCreateEvent_: function(type, kind, address, size, name,
maybe_func) {
function parseState(s) {
switch (s) {
case '': return CodeMap.CodeState.COMPILED;
case '~': return CodeMap.CodeState.OPTIMIZABLE;
case '*': return CodeMap.CodeState.OPTIMIZED;
}
throw new Error('unknown code state: ' + s);
}
if (maybe_func.length) {
var funcAddr = parseInt(maybe_func[0]);
var state = parseState(maybe_func[1]);
var func = this.code_map_.findDynamicEntryByStartAddress(funcAddr);
if (!func) {
func = new FunctionEntry(name);
func.kind = kind;
this.code_map_.addCode(funcAddr, func);
} else if (func.name !== name) {
// Function object has been overwritten with a new one.
func.name = name;
}
var entry = this.code_map_.findDynamicEntryByStartAddress(address);
if (entry) {
if (entry.size === size && entry.func === func) {
// Entry state has changed.
entry.state = state;
}
} else {
entry = new DynamicFuncCodeEntry(size, type, func, state);
entry.kind = kind;
this.code_map_.addCode(address, entry);
}
} else {
var code_entry = new CodeEntry(size, name);
code_entry.kind = kind;
this.code_map_.addCode(address, code_entry);
}
},
processCodeMoveEvent_: function(from, to) {
this.code_map_.moveCode(from, to);
},
processCodeDeleteEvent_: function(address) {
this.code_map_.deleteCode(address);
},
processSharedLibrary_: function(name, start, end) {
var code_entry = new CodeEntry(end - start, name,
CodeEntry.TYPE.SHARED_LIB);
code_entry.kind = -3; // External code kind.
for (var i = 0; i < kV8BinarySuffixes.length; i++) {
var suffix = kV8BinarySuffixes[i];
if (name.indexOf(suffix, name.length - suffix.length) >= 0) {
code_entry.kind = -1; // V8 runtime code kind.
break;
}
}
this.code_map_.addLibrary(start, code_entry);
},
processCppSymbol_: function(address, size, name) {
var code_entry = new CodeEntry(size, name, CodeEntry.TYPE.CPP);
code_entry.kind = -1;
this.code_map_.addStaticCode(address, code_entry);
},
processTickEvent_: function(pc, startInUs, is_external_callback,
tos_or_external_callback, vmstate, stack) {
var startInMs = tr.b.convertUnit(startInUs,
tr.b.UnitScale.Metric.MICRO, tr.b.UnitScale.Metric.MILLI);
function findChildWithEntryID(stackFrame, entryID) {
for (var i = 0; i < stackFrame.children.length; i++) {
if (stackFrame.children[i].entryID == entryID)
return stackFrame.children[i];
}
return undefined;
}
function processStack(pc, func, stack) {
var fullStack = func ? [pc, func] : [pc];
var prevFrame = pc;
for (var i = 0, n = stack.length; i < n; ++i) {
var frame = stack[i];
var firstChar = frame.charAt(0);
if (firstChar === '+' || firstChar === '-') {
// An offset from the previous frame.
prevFrame += parseInt(frame, 16);
fullStack.push(prevFrame);
// Filter out possible 'overflow' string.
} else if (firstChar !== 'o') {
fullStack.push(parseInt(frame, 16));
}
// Otherwise, they will be skipped.
}
return fullStack;
}
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 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.code_map_.findEntry(tos_or_external_callback);
if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction())
tos_or_external_callback = 0;
}
var processedStack = processStack(pc, tos_or_external_callback, stack);
var lastStackFrame = this.root_stack_frame_;
// v8 log stacks are inverted, leaf first and the root at the end.
processedStack = processedStack.reverse();
for (var i = 0, n = processedStack.length; i < n; i++) {
var frame = processedStack[i];
if (!frame)
break;
var entry = this.code_map_.findEntry(frame);
if (!entry && i !== 0) {
continue;
}
var sourceInfo = undefined;
if (entry && entry.type === CodeEntry.TYPE.CPP) {
var libEntry = this.code_map_.findEntryInLibraries(frame);
if (libEntry)
sourceInfo = libEntry.name;
}
var entryID = entry ? entry.id : 'Unknown';
var childFrame = findChildWithEntryID(lastStackFrame, entryID);
if (childFrame === undefined) {
var entryName = entry ? entry.name : 'Unknown';
lastStackFrame = new tr.model.StackFrame(
lastStackFrame, 'v8sf-' + tr.b.GUID.allocateSimple(), entryName,
ColorScheme.getColorIdForGeneralPurposeString(entryName),
sourceInfo);
lastStackFrame.entryID = entryID;
this.model_.addStackFrame(lastStackFrame);
} else {
lastStackFrame = childFrame;
}
}
if (lastStackFrame !== this.root_stack_frame_) {
var sample = new tr.model.Sample(
undefined, this.v8_thread_, 'V8 PC',
startInMs, lastStackFrame, 1);
this.model_.samples.push(sample);
}
},
processDistortion_: function(distortion_in_picoseconds) {
// Do nothing.
},
processPlotRange_: function(start, end) {
// Do nothing.
},
processV8Version_: function(major, minor, build, patch, candidate) {
// Do nothing.
},
/**
* Walks through the events_ list and outputs the structures discovered to
* model_.
*/
importEvents: function() {
var logreader = new tr.e.importer.v8.LogReader(
{ 'timer-event' : {
parsers: [null, parseInt, parseInt],
processor: this.processTimerEvent_.bind(this)
},
'shared-library': {
parsers: [null, parseInt, parseInt],
processor: this.processSharedLibrary_.bind(this)
},
'timer-event-start' : {
parsers: [null, parseInt],
processor: this.processTimerEventStart_.bind(this)
},
'timer-event-end' : {
parsers: [null, parseInt],
processor: this.processTimerEventEnd_.bind(this)
},
'code-creation': {
parsers: [null, parseInt, parseInt, parseInt, null, 'var-args'],
processor: this.processCodeCreateEvent_.bind(this)
},
'code-move': {
parsers: [parseInt, parseInt],
processor: this.processCodeMoveEvent_.bind(this)
},
'code-delete': {
parsers: [parseInt],
processor: this.processCodeDeleteEvent_.bind(this)
},
'cpp': {
parsers: [parseInt, parseInt, null],
processor: this.processCppSymbol_.bind(this)
},
'tick': {
parsers: [parseInt, parseInt, parseInt, parseInt, parseInt,
'var-args'],
processor: this.processTickEvent_.bind(this)
},
'distortion': {
parsers: [parseInt],
processor: this.processDistortion_.bind(this)
},
'plot-range': {
parsers: [parseInt, parseInt],
processor: this.processPlotRange_.bind(this)
},
'v8-version': {
parsers: [parseInt, parseInt, parseInt, parseInt, parseInt],
processor: this.processV8Version_.bind(this)
}
});
this.v8_timer_thread_ =
this.model_.getOrCreateProcess(-32).getOrCreateThread(1);
this.v8_timer_thread_.name = 'V8 Timers';
this.v8_thread_ =
this.model_.getOrCreateProcess(-32).getOrCreateThread(2);
this.v8_thread_.name = 'V8';
var lines = this.logData_.split('\n');
for (var i = 0; i < lines.length; i++) {
logreader.processLogLine(lines[i]);
}
// The processing of samples adds all the root stack frame to
// this.root_stack_frame_. But, we don't want that stack frame in the real
// model. So get rid of it.
this.root_stack_frame_.removeAllChildren();
function addSlices(slices, thread) {
for (var i = 0; i < slices.length; i++) {
var duration = slices[i].end - slices[i].start;
var slice = new tr.model.ThreadSlice('v8', slices[i].name,
ColorScheme.getColorIdForGeneralPurposeString(slices[i].name),
slices[i].start, {}, duration);
thread.sliceGroup.pushSlice(slice);
addSlices(slices[i].children, thread);
}
}
addSlices(this.v8_stack_timeline_, this.v8_thread_);
}
};
tr.importer.Importer.register(V8LogImporter);
return {
V8LogImporter: V8LogImporter
};
});
</script>