| // 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: [null, parseInt, parseInt, parseInt], | |
| processor: this.processSharedLibrary }, | |
| 'code-creation': { | |
| parsers: [null, parseInt, parseInt, parseInt, parseInt, | |
| null, 'var-args'], | |
| processor: this.processCodeCreation }, | |
| 'code-deopt': { | |
| parsers: [parseInt, parseInt, parseInt, parseInt, parseInt, | |
| null, null, null], | |
| 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, null, null, null], | |
| processor: this.processCodeSourceInfo }, | |
| 'script': { | |
| parsers: [parseInt, null, null], | |
| processor: this.processCodeScript }, | |
| 'sfi-move': { parsers: [parseInt, parseInt], | |
| processor: this.processFunctionMove }, | |
| 'active-runtime-timer': { | |
| parsers: [null], | |
| processor: this.processRuntimeTimerEvent }, | |
| 'tick': { | |
| parsers: [parseInt, parseInt, parseInt, | |
| parseInt, parseInt, 'var-args'], | |
| processor: this.processTick }, | |
| 'heap-sample-begin': { parsers: [null, null, parseInt], | |
| processor: this.processHeapSampleBegin }, | |
| 'heap-sample-end': { parsers: [null, null], | |
| processor: this.processHeapSampleEnd }, | |
| 'timer-event-start' : { parsers: [null, null, null], | |
| processor: this.advanceDistortion }, | |
| 'timer-event-end' : { parsers: [null, null, null], | |
| 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; | |
| this.deserializedEntriesNames_ = []; | |
| 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) { | |
| name = this.deserializedEntriesNames_[start] || name; | |
| 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.processCodeScript = 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 prevEntry; | |
| 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 (prevEntry && !prevEntry.end && | |
| prevEntry.start < funcInfo.start && | |
| prevEntry.start >= libStart && funcInfo.start <= libEnd) { | |
| processorFunc(prevEntry.name, prevEntry.start, funcInfo.start); | |
| } | |
| if (funcInfo.end && | |
| (!prevEntry || prevEntry.start != funcInfo.start) && | |
| funcInfo.start >= libStart && funcInfo.end <= libEnd) { | |
| processorFunc(funcInfo.name, funcInfo.start, funcInfo.end); | |
| } | |
| prevEntry = 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, targetRootFS) { | |
| this.symbols = []; | |
| this.parsePos = 0; | |
| this.nmExec = nmExec; | |
| this.targetRootFS = targetRootFS; | |
| 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; | |
| 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) | |
| ]; | |
| } 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) }; | |
| if (fields[2]) { | |
| funcInfo.size = parseInt(fields[2], 16); | |
| } | |
| } | |
| return funcInfo; | |
| }; | |
| 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})() (.*)$/; | |
| }; | |
| 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, targetRootFS) { | |
| 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)'], | |
| '--target': ['targetRootFS', '', | |
| 'Specify the target root directory for cross environment'], | |
| '--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', | |
| range: 'auto,auto', | |
| distortion: 0, | |
| timedRange: false, | |
| pairwiseTimedRange: false, | |
| onlySummary: false, | |
| runtimeTimerFilter: null, | |
| }; | |
| } | |
| } |