| // Copyright 2009 the V8 project authors. All rights reserved. |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following |
| // disclaimer in the documentation and/or other materials provided |
| // with the distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived |
| // from this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| |
| function inherits(childCtor, parentCtor) { |
| childCtor.prototype.__proto__ = parentCtor.prototype; |
| }; |
| |
| |
| function V8Profile(separateIc) { |
| Profile.call(this); |
| if (!separateIc) { |
| this.skipThisFunction = function(name) { return V8Profile.IC_RE.test(name); }; |
| } |
| }; |
| inherits(V8Profile, Profile); |
| |
| |
| V8Profile.IC_RE = |
| /^(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Call|Load|Store)IC_)/; |
| |
| |
| /** |
| * A thin wrapper around shell's 'read' function showing a file name on error. |
| */ |
| function readFile(fileName) { |
| try { |
| return read(fileName); |
| } catch (e) { |
| print(fileName + ': ' + (e.message || e)); |
| throw e; |
| } |
| } |
| |
| |
| /** |
| * 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 SnapshotLogProcessor() { |
| LogReader.call(this, { |
| 'code-creation': { |
| parsers: [null, parseInt, parseInt, null, 'var-args'], |
| processor: this.processCodeCreation }, |
| 'code-move': { parsers: [parseInt, parseInt], |
| processor: this.processCodeMove }, |
| 'code-delete': { parsers: [parseInt], |
| processor: this.processCodeDelete }, |
| 'function-creation': null, |
| 'function-move': null, |
| 'function-delete': null, |
| 'sfi-move': null, |
| 'snapshot-pos': { parsers: [parseInt, parseInt], |
| processor: this.processSnapshotPosition }}); |
| |
| V8Profile.prototype.handleUnknownCode = function(operation, addr) { |
| var op = Profile.Operation; |
| switch (operation) { |
| case op.MOVE: |
| print('Snapshot: Code move event for unknown code: 0x' + |
| addr.toString(16)); |
| break; |
| case op.DELETE: |
| print('Snapshot: Code delete event for unknown code: 0x' + |
| addr.toString(16)); |
| break; |
| } |
| }; |
| |
| this.profile_ = new V8Profile(); |
| this.serializedEntries_ = []; |
| } |
| inherits(SnapshotLogProcessor, LogReader); |
| |
| |
| SnapshotLogProcessor.prototype.processCodeCreation = function( |
| type, 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, start, size, funcAddr, state); |
| } else { |
| this.profile_.addCode(type, name, start, size); |
| } |
| }; |
| |
| |
| SnapshotLogProcessor.prototype.processCodeMove = function(from, to) { |
| this.profile_.moveCode(from, to); |
| }; |
| |
| |
| SnapshotLogProcessor.prototype.processCodeDelete = function(start) { |
| this.profile_.deleteCode(start); |
| }; |
| |
| |
| SnapshotLogProcessor.prototype.processSnapshotPosition = function(addr, pos) { |
| this.serializedEntries_[pos] = this.profile_.findEntry(addr); |
| }; |
| |
| |
| SnapshotLogProcessor.prototype.processLogFile = function(fileName) { |
| var contents = readFile(fileName); |
| this.processLogChunk(contents); |
| }; |
| |
| |
| SnapshotLogProcessor.prototype.getSerializedEntryName = function(pos) { |
| var entry = this.serializedEntries_[pos]; |
| return entry ? entry.getRawName() : null; |
| }; |
| |
| |
| function TickProcessor( |
| cppEntriesProvider, separateIc, ignoreUnknown, stateFilter, snapshotLogProcessor) { |
| LogReader.call(this, { |
| 'shared-library': { parsers: [null, parseInt, parseInt], |
| processor: this.processSharedLibrary }, |
| 'code-creation': { |
| parsers: [null, parseInt, parseInt, null, 'var-args'], |
| processor: this.processCodeCreation }, |
| 'code-move': { parsers: [parseInt, parseInt], |
| processor: this.processCodeMove }, |
| 'code-delete': { parsers: [parseInt], |
| processor: this.processCodeDelete }, |
| 'sfi-move': { parsers: [parseInt, parseInt], |
| processor: this.processFunctionMove }, |
| 'snapshot-pos': { parsers: [parseInt, parseInt], |
| processor: this.processSnapshotPosition }, |
| '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 }, |
| 'heap-js-prod-item': { parsers: [null, 'var-args'], |
| processor: this.processJSProducer }, |
| // Ignored events. |
| 'profiler': null, |
| 'function-creation': null, |
| 'function-move': null, |
| 'function-delete': null, |
| 'heap-sample-stats': null, |
| 'heap-sample-item': null, |
| 'heap-js-cons-item': null, |
| 'heap-js-ret-item': null, |
| // Obsolete row types. |
| 'code-allocate': null, |
| 'begin-code-region': null, |
| 'end-code-region': null }); |
| |
| this.cppEntriesProvider_ = cppEntriesProvider; |
| this.ignoreUnknown_ = ignoreUnknown; |
| this.stateFilter_ = stateFilter; |
| this.snapshotLogProcessor_ = snapshotLogProcessor; |
| this.deserializedEntriesNames_ = []; |
| var ticks = this.ticks_ = |
| { total: 0, unaccounted: 0, excluded: 0, gc: 0 }; |
| |
| V8Profile.prototype.handleUnknownCode = function( |
| operation, addr, opt_stackPos) { |
| var op = Profile.Operation; |
| switch (operation) { |
| case op.MOVE: |
| print('Code move event for unknown code: 0x' + addr.toString(16)); |
| break; |
| case op.DELETE: |
| print('Code delete event for unknown code: 0x' + addr.toString(16)); |
| break; |
| case op.TICK: |
| // Only unknown PCs (the first frame) are reported as unaccounted, |
| // otherwise tick balance will be corrupted (this behavior is compatible |
| // with the original tickprocessor.py script.) |
| if (opt_stackPos == 0) { |
| ticks.unaccounted++; |
| } |
| break; |
| } |
| }; |
| |
| this.profile_ = new V8Profile(separateIc); |
| this.codeTypes_ = {}; |
| // Count each tick as a time unit. |
| this.viewBuilder_ = new ViewBuilder(1); |
| this.lastLogFileName_ = null; |
| |
| this.generation_ = 1; |
| this.currentProducerProfile_ = null; |
| }; |
| inherits(TickProcessor, LogReader); |
| |
| |
| TickProcessor.VmStates = { |
| JS: 0, |
| GC: 1, |
| COMPILER: 2, |
| OTHER: 3, |
| EXTERNAL: 4 |
| }; |
| |
| |
| 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 = 2.0; |
| |
| |
| /** |
| * @override |
| */ |
| TickProcessor.prototype.printError = function(str) { |
| print(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 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) { |
| var entry = this.profile_.addLibrary(name, startAddr, endAddr); |
| this.setCodeType(entry.getName(), 'SHARED_LIB'); |
| |
| var self = this; |
| var libFuncs = this.cppEntriesProvider_.parseVmSymbols( |
| name, startAddr, endAddr, function(fName, fStart, fEnd) { |
| self.profile_.addStaticCode(fName, fStart, fEnd); |
| self.setCodeType(fName, 'CPP'); |
| }); |
| }; |
| |
| |
| TickProcessor.prototype.processCodeCreation = function( |
| type, start, size, name, 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, start, size, funcAddr, state); |
| } else { |
| this.profile_.addCode(type, name, start, size); |
| } |
| }; |
| |
| |
| TickProcessor.prototype.processCodeMove = function(from, to) { |
| this.profile_.moveCode(from, to); |
| }; |
| |
| |
| TickProcessor.prototype.processCodeDelete = function(start) { |
| this.profile_.deleteCode(start); |
| }; |
| |
| |
| TickProcessor.prototype.processFunctionMove = function(from, to) { |
| this.profile_.moveFunc(from, to); |
| }; |
| |
| |
| TickProcessor.prototype.processSnapshotPosition = function(addr, pos) { |
| if (this.snapshotLogProcessor_) { |
| this.deserializedEntriesNames_[addr] = |
| this.snapshotLogProcessor_.getSerializedEntryName(pos); |
| } |
| }; |
| |
| |
| TickProcessor.prototype.includeTick = function(vmState) { |
| return this.stateFilter_ == null || this.stateFilter_ == vmState; |
| }; |
| |
| TickProcessor.prototype.processTick = function(pc, |
| sp, |
| is_external_callback, |
| tos_or_external_callback, |
| vmState, |
| stack) { |
| 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(this.processStack(pc, tos_or_external_callback, stack)); |
| }; |
| |
| |
| 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.processJSProducer = function(constructor, stack) { |
| if (!this.currentProducerProfile_) return; |
| if (stack.length == 0) return; |
| var first = stack.shift(); |
| var processedStack = |
| this.profile_.resolveAndFilterFuncs_(this.processStack(first, 0, stack)); |
| processedStack.unshift(constructor); |
| this.currentProducerProfile_.addPath(processedStack); |
| }; |
| |
| |
| TickProcessor.prototype.printStatistics = function() { |
| print('Statistical profiling result from ' + this.lastLogFileName_ + |
| ', (' + this.ticks_.total + |
| ' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' + |
| this.ticks_.excluded + ' excluded).'); |
| |
| if (this.ticks_.total == 0) return; |
| |
| // Print the unknown ticks percentage if they are not ignored. |
| if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) { |
| this.printHeader('Unknown'); |
| this.printCounter(this.ticks_.unaccounted, this.ticks_.total); |
| } |
| |
| var flatProfile = this.profile_.getFlatProfile(); |
| var flatView = this.viewBuilder_.buildView(flatProfile); |
| // Sort by self time, desc, then by name, desc. |
| flatView.sort(function(rec1, rec2) { |
| return rec2.selfTime - rec1.selfTime || |
| (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); |
| var totalTicks = this.ticks_.total; |
| if (this.ignoreUnknown_) { |
| totalTicks -= this.ticks_.unaccounted; |
| } |
| // Our total time contains all the ticks encountered, |
| // while profile only knows about the filtered ticks. |
| flatView.head.totalTime = totalTicks; |
| |
| // Count library ticks |
| var flatViewNodes = flatView.head.children; |
| var self = this; |
| var libraryTicks = 0; |
| this.processProfile(flatViewNodes, |
| function(name) { return self.isSharedLibrary(name); }, |
| function(rec) { libraryTicks += rec.selfTime; }); |
| var nonLibraryTicks = totalTicks - libraryTicks; |
| |
| this.printHeader('Shared libraries'); |
| this.printEntries(flatViewNodes, null, |
| function(name) { return self.isSharedLibrary(name); }); |
| |
| this.printHeader('JavaScript'); |
| this.printEntries(flatViewNodes, nonLibraryTicks, |
| function(name) { return self.isJsCode(name); }); |
| |
| this.printHeader('C++'); |
| this.printEntries(flatViewNodes, nonLibraryTicks, |
| function(name) { return self.isCppCode(name); }); |
| |
| this.printHeader('GC'); |
| this.printCounter(this.ticks_.gc, totalTicks); |
| |
| this.printHeavyProfHeader(); |
| var heavyProfile = this.profile_.getBottomUpProfile(); |
| var heavyView = this.viewBuilder_.buildView(heavyProfile); |
| // To show the same percentages as in the flat profile. |
| heavyView.head.totalTime = totalTicks; |
| // Sort by total time, desc, then by name, desc. |
| heavyView.sort(function(rec1, rec2) { |
| return rec2.totalTime - rec1.totalTime || |
| (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); |
| this.printHeavyProfile(heavyView.head.children); |
| }; |
| |
| |
| function padLeft(s, len) { |
| s = s.toString(); |
| if (s.length < len) { |
| 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.printHeavyProfHeader = function() { |
| print('\n [Bottom up (heavy) profile]:'); |
| print(' Note: percentage shows a share of a particular caller in the ' + |
| 'total\n' + |
| ' amount of its parent calls.'); |
| print(' Callers occupying less than ' + |
| TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) + |
| '% are not shown.\n'); |
| print(' ticks parent name'); |
| }; |
| |
| |
| TickProcessor.prototype.printCounter = function(ticksCount, totalTicksCount) { |
| var pct = ticksCount * 100.0 / totalTicksCount; |
| print(' ' + padLeft(ticksCount, 5) + ' ' + padLeft(pct.toFixed(1), 5) + '%'); |
| }; |
| |
| |
| TickProcessor.prototype.processProfile = function( |
| profile, filterP, func) { |
| for (var i = 0, n = profile.length; i < n; ++i) { |
| var rec = profile[i]; |
| if (!filterP(rec.internalFuncName)) { |
| continue; |
| } |
| func(rec); |
| } |
| }; |
| |
| |
| TickProcessor.prototype.printEntries = function( |
| profile, nonLibTicks, filterP) { |
| this.processProfile(profile, filterP, function (rec) { |
| if (rec.selfTime == 0) return; |
| var nonLibPct = nonLibTicks != null ? |
| rec.selfTime * 100.0 / nonLibTicks : 0.0; |
| print(' ' + padLeft(rec.selfTime, 5) + ' ' + |
| padLeft(rec.selfPercent.toFixed(1), 5) + '% ' + |
| padLeft(nonLibPct.toFixed(1), 5) + '% ' + |
| rec.internalFuncName); |
| }); |
| }; |
| |
| |
| TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) { |
| var self = this; |
| var indent = opt_indent || 0; |
| var indentStr = padLeft('', indent); |
| this.processProfile(profile, function() { return true; }, function (rec) { |
| // Cut off too infrequent callers. |
| if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return; |
| print(' ' + padLeft(rec.totalTime, 5) + ' ' + |
| padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' + |
| indentStr + rec.internalFuncName); |
| // Limit backtrace depth. |
| if (indent < 10) { |
| self.printHeavyProfile(rec.children, indent + 2); |
| } |
| // Delimit top-level functions. |
| if (indent == 0) { |
| print(''); |
| } |
| }); |
| }; |
| |
| |
| function CppEntriesProvider() { |
| }; |
| |
| |
| CppEntriesProvider.prototype.parseVmSymbols = function( |
| libName, libStart, libEnd, processorFunc) { |
| 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 && funcInfo.start < libEnd - libStart) { |
| funcInfo.start += libStart; |
| } |
| 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) { |
| this.symbols = []; |
| this.parsePos = 0; |
| this.nmExec = nmExec; |
| 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; |
| 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) { |
| UnixCppEntriesProvider.call(this, nmExec); |
| // Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups. |
| this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ()[iItT] (.*)$/; |
| }; |
| inherits(MacCppEntriesProvider, UnixCppEntriesProvider); |
| |
| |
| MacCppEntriesProvider.prototype.loadSymbols = function(libName) { |
| this.parsePos = 0; |
| try { |
| this.symbols = [os.system(this.nmExec, ['-n', '-f', libName], -1, -1), '']; |
| } catch (e) { |
| // If the library cannot be found on this system let's not panic. |
| this.symbols = ''; |
| } |
| }; |
| |
| |
| function WindowsCppEntriesProvider() { |
| 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) { |
| 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('::'); |
| }; |
| |
| |
| function ArgumentsProcessor(args) { |
| this.args_ = args; |
| this.result_ = ArgumentsProcessor.DEFAULTS; |
| |
| this.argsDispatch_ = { |
| '-j': ['stateFilter', TickProcessor.VmStates.JS, |
| 'Show only ticks from JS VM state'], |
| '-g': ['stateFilter', TickProcessor.VmStates.GC, |
| 'Show only ticks from GC VM state'], |
| '-c': ['stateFilter', TickProcessor.VmStates.COMPILER, |
| 'Show only ticks from COMPILER VM state'], |
| '-o': ['stateFilter', TickProcessor.VmStates.OTHER, |
| 'Show only ticks from OTHER VM state'], |
| '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL, |
| 'Show only ticks from EXTERNAL VM state'], |
| '--ignore-unknown': ['ignoreUnknown', true, |
| 'Exclude ticks of unknown code entries from processing'], |
| '--separate-ic': ['separateIc', true, |
| 'Separate IC entries'], |
| '--unix': ['platform', 'unix', |
| 'Specify that we are running on *nix platform'], |
| '--windows': ['platform', 'windows', |
| 'Specify that we are running on Windows platform'], |
| '--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)'], |
| '--snapshot-log': ['snapshotLogFileName', 'snapshot.log', |
| 'Specify snapshot log file to use (e.g. --snapshot-log=snapshot.log)'] |
| }; |
| this.argsDispatch_['--js'] = this.argsDispatch_['-j']; |
| this.argsDispatch_['--gc'] = this.argsDispatch_['-g']; |
| this.argsDispatch_['--compiler'] = this.argsDispatch_['-c']; |
| this.argsDispatch_['--other'] = this.argsDispatch_['-o']; |
| this.argsDispatch_['--external'] = this.argsDispatch_['-e']; |
| }; |
| |
| |
| ArgumentsProcessor.DEFAULTS = { |
| logFileName: 'v8.log', |
| snapshotLogFileName: null, |
| platform: 'unix', |
| stateFilter: null, |
| ignoreUnknown: false, |
| separateIc: false, |
| nm: 'nm' |
| }; |
| |
| |
| ArgumentsProcessor.prototype.parse = function() { |
| while (this.args_.length) { |
| var arg = this.args_[0]; |
| if (arg.charAt(0) != '-') { |
| break; |
| } |
| this.args_.shift(); |
| var userValue = null; |
| var eqPos = arg.indexOf('='); |
| if (eqPos != -1) { |
| userValue = arg.substr(eqPos + 1); |
| arg = arg.substr(0, eqPos); |
| } |
| if (arg in this.argsDispatch_) { |
| var dispatch = this.argsDispatch_[arg]; |
| this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue; |
| } else { |
| return false; |
| } |
| } |
| |
| if (this.args_.length >= 1) { |
| this.result_.logFileName = this.args_.shift(); |
| } |
| return true; |
| }; |
| |
| |
| ArgumentsProcessor.prototype.result = function() { |
| return this.result_; |
| }; |
| |
| |
| ArgumentsProcessor.prototype.printUsageAndExit = function() { |
| |
| function padRight(s, len) { |
| s = s.toString(); |
| if (s.length < len) { |
| s = s + (new Array(len - s.length + 1).join(' ')); |
| } |
| return s; |
| } |
| |
| print('Cmdline args: [options] [log-file-name]\n' + |
| 'Default log file name is "' + |
| ArgumentsProcessor.DEFAULTS.logFileName + '".\n'); |
| print('Options:'); |
| for (var arg in this.argsDispatch_) { |
| var synonims = [arg]; |
| var dispatch = this.argsDispatch_[arg]; |
| for (var synArg in this.argsDispatch_) { |
| if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) { |
| synonims.push(synArg); |
| delete this.argsDispatch_[synArg]; |
| } |
| } |
| print(' ' + padRight(synonims.join(', '), 20) + dispatch[2]); |
| } |
| quit(2); |
| }; |
| |