├── ChangeLog.txt ├── License ├── README.md ├── bin ├── stdio.js └── tickprocessor-driver.js ├── lib ├── codemap.js ├── composer.js ├── consarray.js ├── csvparser.js ├── logreader.js ├── profile.js ├── profile_view.js ├── splaytree.js └── tickprocessor.js ├── package.json └── test ├── generate.js └── v8-3.22.24.19.log /ChangeLog.txt: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | - LogProcessor now tries to giess v8.log format and it should handle 3 | profiles from both node 0.10.x and 0.11.x 4 | - added timeline gnuplot script generation 5 | 6 | 0.0.6 7 | - --ignore-unknown flag 8 | 9 | 0.0.2 10 | - it's now possible to use large files (previous version failed on 5Gb 11 | example). readableStream is used now instead of 'read all & split' 12 | - fixe for argument parser initialisation. log & snapshot file name 13 | and other options work now 14 | - separate source files `require`d instead of joining all sources into 15 | single bundle 16 | - scripts updated from V8 3.14.5 17 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Andrey Sidorov (sidorares@yandex.ru) and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | v8.log processor based on scripts in v8 distribution. 2 | Allows you to profile V8-based programs without installing V8 from source. 3 | 4 | Note that starting from version v5.2.0 node distribution includes v8.log processor - see [#node/4021](https://github.com/nodejs/node/pull/4021). To use it, type `node --prof-process` 5 | 6 | ### Install 7 | $ npm install -g tick 8 | 9 | ### Usage 10 | 11 | See [V8 profiler page](https://github.com/v8/v8/wiki/V8%20Profiler) for basic introduction to v8.log 12 | 13 | $ node --prof yourprogram 14 | $ node-tick-processor 15 | 16 | ### See also 17 | 18 | `takeSnapshot` & `startProfiling` & `stopProfiling` V8 profiler API exposed to node.js: [v8-profiler](https://github.com/dannycoates/v8-profiler) 19 | 20 | 21 | ### V8 Performance optimisation resources 22 | 23 | - [v8-perf](https://thlorenz.github.io/v8-perf/) 24 | - [Trevor Norris notes](https://gist.github.com/trevnorris/6fea5ab2632dff8b5b25#file-perf-training-syllabus-md) 25 | - [V8 optimisations killers](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) 26 | - [I-want-to-optimize-my-JS-application-on-V8 checklist](http://mrale.ph/blog/2011/12/18/v8-optimization-checklist.html) ( + [another listlist](http://mrale.ph/v8/resources.html) ) by @mraleph 27 | - https://github.com/thlorenz/iojs-inspect-entire-stack 28 | - [v8 bailout reasons explained](https://github.com/vhf/v8-bailout-reasons) 29 | -------------------------------------------------------------------------------- /bin/stdio.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var tickProcessorModule = require('../lib/tickprocessor'); 4 | var composer = require('../lib/composer'); 5 | var ArgumentsProcessor = tickProcessorModule.ArgumentsProcessor; 6 | var TickProcessor = tickProcessorModule.TickProcessor; 7 | var SnapshotLogProcessor = tickProcessorModule.SnapshotLogProcessor; 8 | var PlotScriptComposer = composer.PlotScriptComposer; 9 | var processFileLines = tickProcessorModule.processFileLines; 10 | 11 | // Copyright 2013 the V8 project authors. All rights reserved. 12 | // Redistribution and use in source and binary forms, with or without 13 | // modification, are permitted provided that the following conditions are 14 | // met: 15 | // 16 | // * Redistributions of source code must retain the above copyright 17 | // notice, this list of conditions and the following disclaimer. 18 | // * Redistributions in binary form must reproduce the above 19 | // copyright notice, this list of conditions and the following 20 | // disclaimer in the documentation and/or other materials provided 21 | // with the distribution. 22 | // * Neither the name of Google Inc. nor the names of its 23 | // contributors may be used to endorse or promote products derived 24 | // from this software without specific prior written permission. 25 | // 26 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | 38 | var args = process.argv.slice(2); 39 | 40 | var processor = new ArgumentsProcessor(args); 41 | var distortion_per_entry = 0; 42 | var range_start_override = undefined; 43 | var range_end_override = undefined; 44 | 45 | if (!processor.parse()) processor.printUsageAndExit(); 46 | var result = processor.result(); 47 | var distortion = parseInt(result.distortion); 48 | if (isNaN(distortion)) processor.printUsageAndExit(); 49 | // Convert picoseconds to milliseconds. 50 | distortion_per_entry = distortion / 1000000; 51 | var rangelimits = result.range.split(","); 52 | var range_start = parseInt(rangelimits[0]); 53 | var range_end = parseInt(rangelimits[1]); 54 | if (!isNaN(range_start)) range_start_override = range_start; 55 | if (!isNaN(range_end)) range_end_override = range_end; 56 | 57 | var kResX = 1600; 58 | var kResY = 700; 59 | function log_error(text) { 60 | console.error(text); 61 | quit(1); 62 | } 63 | var psc = new PlotScriptComposer(kResX, kResY, log_error); 64 | var collector = psc.collectData(distortion_per_entry); 65 | 66 | processFileLines(result.logFileName, function(readline) { 67 | collector.processLine(readline); 68 | }, function() { 69 | collector.onDone(); 70 | 71 | psc.findPlotRange(range_start_override, range_end_override); 72 | console.log("set terminal pngcairo size " + kResX + "," + kResY + 73 | " enhanced font 'Helvetica,10'"); 74 | psc.assembleOutput(console.log); 75 | }); 76 | -------------------------------------------------------------------------------- /bin/tickprocessor-driver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var tickProcessorModule = require('../lib/tickprocessor'); 4 | var ArgumentsProcessor = tickProcessorModule.ArgumentsProcessor; 5 | var TickProcessor = tickProcessorModule.TickProcessor; 6 | var SnapshotLogProcessor = tickProcessorModule.SnapshotLogProcessor; 7 | 8 | // Copyright 2012 the V8 project authors. All rights reserved. 9 | // Redistribution and use in source and binary forms, with or without 10 | // modification, are permitted provided that the following conditions are 11 | // met: 12 | // 13 | // * Redistributions of source code must retain the above copyright 14 | // notice, this list of conditions and the following disclaimer. 15 | // * Redistributions in binary form must reproduce the above 16 | // copyright notice, this list of conditions and the following 17 | // disclaimer in the documentation and/or other materials provided 18 | // with the distribution. 19 | // * Neither the name of Google Inc. nor the names of its 20 | // contributors may be used to endorse or promote products derived 21 | // from this software without specific prior written permission. 22 | // 23 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | 36 | // Tick Processor's code flow. 37 | 38 | function processArguments(args) { 39 | var processor = new ArgumentsProcessor(args); 40 | if (processor.parse()) { 41 | return processor.result(); 42 | } else { 43 | processor.printUsageAndExit(); 44 | } 45 | } 46 | 47 | var entriesProviders = { 48 | 'unix': tickProcessorModule.UnixCppEntriesProvider, 49 | 'windows': tickProcessorModule.WindowsCppEntriesProvider, 50 | 'mac': tickProcessorModule.MacCppEntriesProvider 51 | }; 52 | 53 | var params = processArguments(process.argv.slice(2)); 54 | var snapshotLogProcessor; 55 | if (params.snapshotLogFileName) { 56 | snapshotLogProcessor = new SnapshotLogProcessor(params.ignoreUnknown); 57 | snapshotLogProcessor.processLogFile(params.snapshotLogFileName, processTicks); 58 | } 59 | 60 | function processTicks() { 61 | var tickProcessor = new TickProcessor( 62 | new (entriesProviders[params.platform])(params.nm, params.targetRootFS), 63 | params.separateIc, 64 | params.callGraphSize, 65 | params.ignoreUnknown, 66 | params.stateFilter, 67 | snapshotLogProcessor, 68 | params.distortion, 69 | params.range, 70 | params.sourceMap); 71 | tickProcessor.processLogFile(params.logFileName, tickProcessor.printStatistics.bind(tickProcessor)); 72 | } 73 | 74 | processTicks(); 75 | -------------------------------------------------------------------------------- /lib/codemap.js: -------------------------------------------------------------------------------- 1 | var SplayTree = require('./splaytree'); 2 | // Copyright 2009 the V8 project authors. All rights reserved. 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following 11 | // disclaimer in the documentation and/or other materials provided 12 | // with the distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived 15 | // from this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | /** 31 | * Constructs a mapper that maps addresses into code entries. 32 | * 33 | * @constructor 34 | */ 35 | function CodeMap() { 36 | /** 37 | * Dynamic code entries. Used for JIT compiled code. 38 | */ 39 | this.dynamics_ = new SplayTree(); 40 | 41 | /** 42 | * Name generator for entries having duplicate names. 43 | */ 44 | this.dynamicsNameGen_ = new CodeMap.NameGenerator(); 45 | 46 | /** 47 | * Static code entries. Used for statically compiled code. 48 | */ 49 | this.statics_ = new SplayTree(); 50 | 51 | /** 52 | * Libraries entries. Used for the whole static code libraries. 53 | */ 54 | this.libraries_ = new SplayTree(); 55 | 56 | /** 57 | * Map of memory pages occupied with static code. 58 | */ 59 | this.pages_ = []; 60 | }; 61 | 62 | 63 | /** 64 | * The number of alignment bits in a page address. 65 | */ 66 | CodeMap.PAGE_ALIGNMENT = 12; 67 | 68 | 69 | /** 70 | * Page size in bytes. 71 | */ 72 | CodeMap.PAGE_SIZE = 73 | 1 << CodeMap.PAGE_ALIGNMENT; 74 | 75 | 76 | /** 77 | * Adds a dynamic (i.e. moveable and discardable) code entry. 78 | * 79 | * @param {number} start The starting address. 80 | * @param {CodeMap.CodeEntry} codeEntry Code entry object. 81 | */ 82 | CodeMap.prototype.addCode = function(start, codeEntry) { 83 | this.deleteAllCoveredNodes_(this.dynamics_, start, start + codeEntry.size); 84 | this.dynamics_.insert(start, codeEntry); 85 | }; 86 | 87 | 88 | /** 89 | * Moves a dynamic code entry. Throws an exception if there is no dynamic 90 | * code entry with the specified starting address. 91 | * 92 | * @param {number} from The starting address of the entry being moved. 93 | * @param {number} to The destination address. 94 | */ 95 | CodeMap.prototype.moveCode = function(from, to) { 96 | var removedNode = this.dynamics_.remove(from); 97 | this.deleteAllCoveredNodes_(this.dynamics_, to, to + removedNode.value.size); 98 | this.dynamics_.insert(to, removedNode.value); 99 | }; 100 | 101 | 102 | /** 103 | * Discards a dynamic code entry. Throws an exception if there is no dynamic 104 | * code entry with the specified starting address. 105 | * 106 | * @param {number} start The starting address of the entry being deleted. 107 | */ 108 | CodeMap.prototype.deleteCode = function(start) { 109 | var removedNode = this.dynamics_.remove(start); 110 | }; 111 | 112 | 113 | /** 114 | * Adds a library entry. 115 | * 116 | * @param {number} start The starting address. 117 | * @param {CodeMap.CodeEntry} codeEntry Code entry object. 118 | */ 119 | CodeMap.prototype.addLibrary = function( 120 | start, codeEntry) { 121 | this.markPages_(start, start + codeEntry.size); 122 | this.libraries_.insert(start, codeEntry); 123 | }; 124 | 125 | 126 | /** 127 | * Adds a static code entry. 128 | * 129 | * @param {number} start The starting address. 130 | * @param {CodeMap.CodeEntry} codeEntry Code entry object. 131 | */ 132 | CodeMap.prototype.addStaticCode = function( 133 | start, codeEntry) { 134 | this.statics_.insert(start, codeEntry); 135 | }; 136 | 137 | 138 | /** 139 | * @private 140 | */ 141 | CodeMap.prototype.markPages_ = function(start, end) { 142 | for (var addr = start; addr <= end; 143 | addr += CodeMap.PAGE_SIZE) { 144 | this.pages_[addr >>> CodeMap.PAGE_ALIGNMENT] = 1; 145 | } 146 | }; 147 | 148 | 149 | /** 150 | * @private 151 | */ 152 | CodeMap.prototype.deleteAllCoveredNodes_ = function(tree, start, end) { 153 | var to_delete = []; 154 | var addr = end - 1; 155 | while (addr >= start) { 156 | var node = tree.findGreatestLessThan(addr); 157 | if (!node) break; 158 | var start2 = node.key, end2 = start2 + node.value.size; 159 | if (start2 < end && start < end2) to_delete.push(start2); 160 | addr = start2 - 1; 161 | } 162 | for (var i = 0, l = to_delete.length; i < l; ++i) tree.remove(to_delete[i]); 163 | }; 164 | 165 | 166 | /** 167 | * @private 168 | */ 169 | CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) { 170 | return addr >= node.key && addr < (node.key + node.value.size); 171 | }; 172 | 173 | 174 | /** 175 | * @private 176 | */ 177 | CodeMap.prototype.findInTree_ = function(tree, addr) { 178 | var node = tree.findGreatestLessThan(addr); 179 | return node && this.isAddressBelongsTo_(addr, node) ? node.value : null; 180 | }; 181 | 182 | 183 | /** 184 | * Finds a code entry that contains the specified address. Both static and 185 | * dynamic code entries are considered. 186 | * 187 | * @param {number} addr Address. 188 | */ 189 | CodeMap.prototype.findEntry = function(addr) { 190 | var pageAddr = addr >>> CodeMap.PAGE_ALIGNMENT; 191 | if (pageAddr in this.pages_) { 192 | // Static code entries can contain "holes" of unnamed code. 193 | // In this case, the whole library is assigned to this address. 194 | return this.findInTree_(this.statics_, addr) || 195 | this.findInTree_(this.libraries_, addr); 196 | } 197 | var min = this.dynamics_.findMin(); 198 | var max = this.dynamics_.findMax(); 199 | if (max != null && addr < (max.key + max.value.size) && addr >= min.key) { 200 | var dynaEntry = this.findInTree_(this.dynamics_, addr); 201 | if (dynaEntry == null) return null; 202 | // Dedupe entry name. 203 | if (!dynaEntry.nameUpdated_) { 204 | dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name); 205 | dynaEntry.nameUpdated_ = true; 206 | } 207 | return dynaEntry; 208 | } 209 | return null; 210 | }; 211 | 212 | 213 | /** 214 | * Returns a dynamic code entry using its starting address. 215 | * 216 | * @param {number} addr Address. 217 | */ 218 | CodeMap.prototype.findDynamicEntryByStartAddress = 219 | function(addr) { 220 | var node = this.dynamics_.find(addr); 221 | return node ? node.value : null; 222 | }; 223 | 224 | 225 | /** 226 | * Returns an array of all dynamic code entries. 227 | */ 228 | CodeMap.prototype.getAllDynamicEntries = function() { 229 | return this.dynamics_.exportValues(); 230 | }; 231 | 232 | 233 | /** 234 | * Returns an array of pairs of all dynamic code entries and their addresses. 235 | */ 236 | CodeMap.prototype.getAllDynamicEntriesWithAddresses = function() { 237 | return this.dynamics_.exportKeysAndValues(); 238 | }; 239 | 240 | 241 | /** 242 | * Returns an array of all static code entries. 243 | */ 244 | CodeMap.prototype.getAllStaticEntries = function() { 245 | return this.statics_.exportValues(); 246 | }; 247 | 248 | 249 | /** 250 | * Returns an array of all libraries entries. 251 | */ 252 | CodeMap.prototype.getAllLibrariesEntries = function() { 253 | return this.libraries_.exportValues(); 254 | }; 255 | 256 | 257 | /** 258 | * Creates a code entry object. 259 | * 260 | * @param {number} size Code entry size in bytes. 261 | * @param {string} opt_name Code entry name. 262 | * @constructor 263 | */ 264 | CodeMap.CodeEntry = function(size, opt_name) { 265 | this.size = size; 266 | this.name = opt_name || ''; 267 | this.nameUpdated_ = false; 268 | }; 269 | 270 | 271 | CodeMap.CodeEntry.prototype.getName = function() { 272 | return this.name; 273 | }; 274 | 275 | 276 | CodeMap.CodeEntry.prototype.toString = function() { 277 | return this.name + ': ' + this.size.toString(16); 278 | }; 279 | 280 | 281 | CodeMap.NameGenerator = function() { 282 | this.knownNames_ = {}; 283 | }; 284 | 285 | 286 | CodeMap.NameGenerator.prototype.getName = function(name) { 287 | if (!(name in this.knownNames_)) { 288 | this.knownNames_[name] = 0; 289 | return name; 290 | } 291 | var count = ++this.knownNames_[name]; 292 | return name + ' {' + count + '}'; 293 | }; 294 | 295 | module.exports.CodeMap = CodeMap; 296 | module.exports.CodeEntry = CodeMap.CodeEntry; 297 | -------------------------------------------------------------------------------- /lib/composer.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 the V8 project authors. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are 4 | // met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above 9 | // copyright notice, this list of conditions and the following 10 | // disclaimer in the documentation and/or other materials provided 11 | // with the distribution. 12 | // * Neither the name of Google Inc. nor the names of its 13 | // contributors may be used to endorse or promote products derived 14 | // from this software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | var codeMapModule = require('./codemap'); 29 | var CodeMap = codeMapModule.CodeMap; 30 | var logReaderModule = require('./logreader'); 31 | var LogReader = logReaderModule.LogReader; 32 | 33 | Array.prototype.top = function() { 34 | if (this.length == 0) return undefined; 35 | return this[this.length - 1]; 36 | } 37 | 38 | 39 | function PlotScriptComposer(kResX, kResY, error_output) { 40 | // Constants. 41 | var kV8BinarySuffixes = ["/d8", "/libv8.so"]; 42 | var kStackFrames = 8; // Stack frames to display in the plot. 43 | 44 | var kTimerEventWidth = 0.33; // Width of each timeline. 45 | var kExecutionFrameWidth = 0.2; // Width of the top stack frame line. 46 | var kStackFrameWidth = 0.1; // Width of the lower stack frame lines. 47 | var kGapWidth = 0.05; // Gap between stack frame lines. 48 | 49 | var kY1Offset = 11; // Offset for stack frame vs. event lines. 50 | var kDeoptRow = 7; // Row displaying deopts. 51 | var kGetTimeHeight = 0.5; // Height of marker displaying timed part. 52 | var kMaxDeoptLength = 4; // Draw size of the largest deopt. 53 | var kPauseLabelPadding = 5; // Padding for pause time labels. 54 | var kNumPauseLabels = 7; // Number of biggest pauses to label. 55 | var kCodeKindLabelPadding = 100; // Padding for code kind labels. 56 | 57 | var kTickHalfDuration = 0.5; // Duration of half a tick in ms. 58 | var kMinRangeLength = 0.0005; // Minimum length for an event in ms. 59 | 60 | var kNumThreads = 2; // Number of threads. 61 | var kExecutionThreadId = 0; // ID of main thread. 62 | 63 | // Init values. 64 | var num_timer_event = kY1Offset + 0.5; 65 | 66 | // Data structures. 67 | function TimerEvent(label, color, pause, thread_id) { 68 | assert(thread_id >= 0 && thread_id < kNumThreads, "invalid thread id"); 69 | this.label = label; 70 | this.color = color; 71 | this.pause = pause; 72 | this.ranges = []; 73 | this.thread_id = thread_id; 74 | this.index = ++num_timer_event; 75 | } 76 | 77 | function CodeKind(color, kinds) { 78 | this.color = color; 79 | this.in_execution = []; 80 | this.stack_frames = []; 81 | for (var i = 0; i < kStackFrames; i++) this.stack_frames.push([]); 82 | this.kinds = kinds; 83 | } 84 | 85 | function Range(start, end) { 86 | this.start = start; // In milliseconds. 87 | this.end = end; // In milliseconds. 88 | } 89 | 90 | function Deopt(time, size) { 91 | this.time = time; // In milliseconds. 92 | this.size = size; // In bytes. 93 | } 94 | 95 | Range.prototype.duration = function() { return this.end - this.start; } 96 | 97 | function Tick(tick) { 98 | this.tick = tick; 99 | } 100 | 101 | var TimerEvents = { 102 | 'V8.Execute': 103 | new TimerEvent("execution", "#000000", false, 0), 104 | 'V8.External': 105 | new TimerEvent("external", "#3399FF", false, 0), 106 | 'V8.CompileFullCode': 107 | new TimerEvent("compile unopt", "#CC0000", true, 0), 108 | 'V8.RecompileSynchronous': 109 | new TimerEvent("recompile sync", "#CC0044", true, 0), 110 | 'V8.RecompileConcurrent': 111 | new TimerEvent("recompile async", "#CC4499", false, 1), 112 | 'V8.CompileEval': 113 | new TimerEvent("compile eval", "#CC4400", true, 0), 114 | 'V8.IcMiss': 115 | new TimerEvent("ic miss", "#CC9900", false, 0), 116 | 'V8.Parse': 117 | new TimerEvent("parse", "#00CC00", true, 0), 118 | 'V8.PreParse': 119 | new TimerEvent("preparse", "#44CC00", true, 0), 120 | 'V8.ParseLazy': 121 | new TimerEvent("lazy parse", "#00CC44", true, 0), 122 | 'V8.GCScavenger': 123 | new TimerEvent("gc scavenge", "#0044CC", true, 0), 124 | 'V8.GCCompactor': 125 | new TimerEvent("gc compaction", "#4444CC", true, 0), 126 | 'V8.GCContext': 127 | new TimerEvent("gc context", "#4400CC", true, 0), 128 | }; 129 | 130 | var CodeKinds = { 131 | 'external ': new CodeKind("#3399FF", [-2]), 132 | 'runtime ': new CodeKind("#000000", [-1]), 133 | 'full code': new CodeKind("#DD0000", [0]), 134 | 'opt code ': new CodeKind("#00EE00", [1]), 135 | 'code stub': new CodeKind("#FF00FF", [2]), 136 | 'built-in ': new CodeKind("#AA00AA", [3]), 137 | 'inl.cache': new CodeKind("#4444AA", 138 | [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]), 139 | 'reg.exp. ': new CodeKind("#0000FF", [15]), 140 | }; 141 | 142 | var code_map = new CodeMap(); 143 | var execution_pauses = []; 144 | var deopts = []; 145 | var gettime = []; 146 | var event_stack = []; 147 | var last_time_stamp = []; 148 | for (var i = 0; i < kNumThreads; i++) { 149 | event_stack[i] = []; 150 | last_time_stamp[i] = -1; 151 | } 152 | 153 | var range_start = undefined; 154 | var range_end = undefined; 155 | var obj_index = 0; 156 | var pause_tolerance = 0.005; // Milliseconds. 157 | var distortion = 0; 158 | 159 | // Utility functions. 160 | function assert(something, message) { 161 | if (!something) { 162 | var error = new Error(message); 163 | error_output(error.stack); 164 | } 165 | } 166 | 167 | function FindCodeKind(kind) { 168 | for (name in CodeKinds) { 169 | if (CodeKinds[name].kinds.indexOf(kind) >= 0) { 170 | return CodeKinds[name]; 171 | } 172 | } 173 | } 174 | 175 | function TicksToRanges(ticks) { 176 | var ranges = []; 177 | for (var i = 0; i < ticks.length; i++) { 178 | var tick = ticks[i].tick; 179 | ranges.push( 180 | new Range(tick - kTickHalfDuration, tick + kTickHalfDuration)); 181 | } 182 | return ranges; 183 | } 184 | 185 | function MergeRanges(ranges) { 186 | ranges.sort(function(a, b) { return a.start - b.start; }); 187 | var result = []; 188 | var j = 0; 189 | for (var i = 0; i < ranges.length; i = j) { 190 | var merge_start = ranges[i].start; 191 | if (merge_start > range_end) break; // Out of plot range. 192 | var merge_end = ranges[i].end; 193 | for (j = i + 1; j < ranges.length; j++) { 194 | var next_range = ranges[j]; 195 | // Don't merge ranges if there is no overlap (incl. merge tolerance). 196 | if (next_range.start > merge_end + pause_tolerance) break; 197 | // Merge ranges. 198 | if (next_range.end > merge_end) { // Extend range end. 199 | merge_end = next_range.end; 200 | } 201 | } 202 | if (merge_end < range_start) continue; // Out of plot range. 203 | if (merge_end < merge_start) continue; // Not an actual range. 204 | result.push(new Range(merge_start, merge_end)); 205 | } 206 | return result; 207 | } 208 | 209 | function RestrictRangesTo(ranges, start, end) { 210 | var result = []; 211 | for (var i = 0; i < ranges.length; i++) { 212 | if (ranges[i].start <= end && ranges[i].end >= start) { 213 | result.push(new Range(Math.max(ranges[i].start, start), 214 | Math.min(ranges[i].end, end))); 215 | } 216 | } 217 | return result; 218 | } 219 | 220 | // Public methods. 221 | this.collectData = function(distortion_per_entry) { 222 | 223 | var last_timestamp = 0; 224 | 225 | // Parse functions. 226 | var parseTimeStamp = function(timestamp) { 227 | int_timestamp = parseInt(timestamp); 228 | assert(int_timestamp >= last_timestamp, "Inconsistent timestamps."); 229 | last_timestamp = int_timestamp; 230 | distortion += distortion_per_entry; 231 | return int_timestamp / 1000 - distortion; 232 | } 233 | 234 | var processTimerEventStart = function(name, start) { 235 | // Find out the thread id. 236 | var new_event = TimerEvents[name]; 237 | if (new_event === undefined) return; 238 | var thread_id = new_event.thread_id; 239 | 240 | start = Math.max(last_time_stamp[thread_id] + kMinRangeLength, start); 241 | 242 | // Last event on this thread is done with the start of this event. 243 | var last_event = event_stack[thread_id].top(); 244 | if (last_event !== undefined) { 245 | var new_range = new Range(last_time_stamp[thread_id], start); 246 | last_event.ranges.push(new_range); 247 | } 248 | event_stack[thread_id].push(new_event); 249 | last_time_stamp[thread_id] = start; 250 | }; 251 | 252 | var processTimerEventEnd = function(name, end) { 253 | // Find out about the thread_id. 254 | var finished_event = TimerEvents[name]; 255 | var thread_id = finished_event.thread_id; 256 | assert(finished_event === event_stack[thread_id].pop(), 257 | "inconsistent event stack"); 258 | 259 | end = Math.max(last_time_stamp[thread_id] + kMinRangeLength, end); 260 | 261 | var new_range = new Range(last_time_stamp[thread_id], end); 262 | finished_event.ranges.push(new_range); 263 | last_time_stamp[thread_id] = end; 264 | }; 265 | 266 | var processCodeCreateEvent = function(type, kind, address, size, name) { 267 | var code_entry = new CodeMap.CodeEntry(size, name); 268 | code_entry.kind = kind; 269 | code_map.addCode(address, code_entry); 270 | }; 271 | 272 | var processCodeMoveEvent = function(from, to) { 273 | code_map.moveCode(from, to); 274 | }; 275 | 276 | var processCodeDeleteEvent = function(address) { 277 | code_map.deleteCode(address); 278 | }; 279 | 280 | var processCodeDeoptEvent = function(time, size) { 281 | deopts.push(new Deopt(time, size)); 282 | } 283 | 284 | var processCurrentTimeEvent = function(time) { 285 | gettime.push(time); 286 | } 287 | 288 | var processSharedLibrary = function(name, start, end) { 289 | var code_entry = new CodeMap.CodeEntry(end - start, name); 290 | code_entry.kind = -3; // External code kind. 291 | for (var i = 0; i < kV8BinarySuffixes.length; i++) { 292 | var suffix = kV8BinarySuffixes[i]; 293 | if (name.indexOf(suffix, name.length - suffix.length) >= 0) { 294 | code_entry.kind = -1; // V8 runtime code kind. 295 | break; 296 | } 297 | } 298 | code_map.addLibrary(start, code_entry); 299 | }; 300 | 301 | var processTickEvent = function( 302 | pc, timer, unused_x, unused_y, vmstate, stack) { 303 | var tick = new Tick(timer); 304 | 305 | var entry = code_map.findEntry(pc); 306 | if (entry) FindCodeKind(entry.kind).in_execution.push(tick); 307 | 308 | for (var i = 0; i < kStackFrames; i++) { 309 | if (!stack[i]) break; 310 | var entry = code_map.findEntry(stack[i]); 311 | if (entry) FindCodeKind(entry.kind).stack_frames[i].push(tick); 312 | } 313 | }; 314 | // Collect data from log. 315 | var logreader = new LogReader( 316 | { 'timer-event-start': { parsers: [null, parseTimeStamp], 317 | processor: processTimerEventStart }, 318 | 'timer-event-end': { parsers: [null, parseTimeStamp], 319 | processor: processTimerEventEnd }, 320 | 'shared-library': { parsers: [null, parseInt, parseInt], 321 | processor: processSharedLibrary }, 322 | 'code-creation': { parsers: [null, parseInt, parseInt, parseInt, null], 323 | processor: processCodeCreateEvent }, 324 | 'code-move': { parsers: [parseInt, parseInt], 325 | processor: processCodeMoveEvent }, 326 | 'code-delete': { parsers: [parseInt], 327 | processor: processCodeDeleteEvent }, 328 | 'code-deopt': { parsers: [parseTimeStamp, parseInt], 329 | processor: processCodeDeoptEvent }, 330 | 'current-time': { parsers: [parseTimeStamp], 331 | processor: processCurrentTimeEvent }, 332 | 'tick': { parsers: [parseInt, parseTimeStamp, 333 | null, null, parseInt, 'var-args'], 334 | processor: processTickEvent } 335 | }); 336 | 337 | return { 338 | processLine: function(line) { 339 | logreader.processLogLine(line); 340 | }, 341 | onDone: function() { 342 | for (name in TimerEvents) { 343 | var event = TimerEvents[name]; 344 | if (!event.pause) continue; 345 | var ranges = event.ranges; 346 | for (var j = 0; j < ranges.length; j++) execution_pauses.push(ranges[j]); 347 | } 348 | execution_pauses = MergeRanges(execution_pauses); 349 | } 350 | }; 351 | }; 352 | 353 | 354 | this.findPlotRange = function( 355 | range_start_override, range_end_override, result_callback) { 356 | var start_found = (range_start_override || range_start_override == 0); 357 | var end_found = (range_end_override || range_end_override == 0); 358 | range_start = start_found ? range_start_override : Infinity; 359 | range_end = end_found ? range_end_override : -Infinity; 360 | 361 | if (!start_found || !end_found) { 362 | for (name in TimerEvents) { 363 | var ranges = TimerEvents[name].ranges; 364 | for (var i = 0; i < ranges.length; i++) { 365 | if (ranges[i].start < range_start && !start_found) { 366 | range_start = ranges[i].start; 367 | } 368 | if (ranges[i].end > range_end && !end_found) { 369 | range_end = ranges[i].end; 370 | } 371 | } 372 | } 373 | 374 | for (codekind in CodeKinds) { 375 | var ticks = CodeKinds[codekind].in_execution; 376 | for (var i = 0; i < ticks.length; i++) { 377 | if (ticks[i].tick < range_start && !start_found) { 378 | range_start = ticks[i].tick; 379 | } 380 | if (ticks[i].tick > range_end && !end_found) { 381 | range_end = ticks[i].tick; 382 | } 383 | } 384 | } 385 | } 386 | // Set pause tolerance to something appropriate for the plot resolution 387 | // to make it easier for gnuplot. 388 | pause_tolerance = (range_end - range_start) / kResX / 10; 389 | 390 | if (typeof result_callback === 'function') { 391 | result_callback(range_start, range_end); 392 | } 393 | }; 394 | 395 | 396 | this.assembleOutput = function(output) { 397 | output("set yrange [0:" + (num_timer_event + 1) + "]"); 398 | output("set xlabel \"execution time in ms\""); 399 | output("set xrange [" + range_start + ":" + range_end + "]"); 400 | output("set style fill pattern 2 bo 1"); 401 | output("set style rect fs solid 1 noborder"); 402 | output("set style line 1 lt 1 lw 1 lc rgb \"#000000\""); 403 | output("set border 15 lw 0.2"); // Draw thin border box. 404 | output("set style line 2 lt 1 lw 1 lc rgb \"#9944CC\""); 405 | output("set xtics out nomirror"); 406 | output("unset key"); 407 | 408 | function DrawBarBase(color, start, end, top, bottom, transparency) { 409 | obj_index++; 410 | command = "set object " + obj_index + " rect"; 411 | command += " from " + start + ", " + top; 412 | command += " to " + end + ", " + bottom; 413 | command += " fc rgb \"" + color + "\""; 414 | if (transparency) { 415 | command += " fs transparent solid " + transparency; 416 | } 417 | output(command); 418 | } 419 | 420 | function DrawBar(row, color, start, end, width) { 421 | DrawBarBase(color, start, end, row + width, row - width); 422 | } 423 | 424 | function DrawHalfBar(row, color, start, end, width) { 425 | DrawBarBase(color, start, end, row, row - width); 426 | } 427 | 428 | var percentages = {}; 429 | var total = 0; 430 | for (var name in TimerEvents) { 431 | var event = TimerEvents[name]; 432 | var ranges = RestrictRangesTo(event.ranges, range_start, range_end); 433 | var sum = 434 | ranges.map(function(range) { return range.duration(); }) 435 | .reduce(function(a, b) { return a + b; }, 0); 436 | percentages[name] = (sum / (range_end - range_start) * 100).toFixed(1); 437 | } 438 | 439 | // Plot deopts. 440 | deopts.sort(function(a, b) { return b.size - a.size; }); 441 | var max_deopt_size = deopts.length > 0 ? deopts[0].size : Infinity; 442 | 443 | for (var i = 0; i < deopts.length; i++) { 444 | var deopt = deopts[i]; 445 | DrawHalfBar(kDeoptRow, "#9944CC", deopt.time, 446 | deopt.time + 10 * pause_tolerance, 447 | deopt.size / max_deopt_size * kMaxDeoptLength); 448 | } 449 | 450 | // Plot current time polls. 451 | if (gettime.length > 1) { 452 | var start = gettime[0]; 453 | var end = gettime.pop(); 454 | DrawBarBase("#0000BB", start, end, kGetTimeHeight, 0, 0.2); 455 | } 456 | 457 | // Name Y-axis. 458 | var ytics = []; 459 | for (name in TimerEvents) { 460 | var index = TimerEvents[name].index; 461 | var label = TimerEvents[name].label; 462 | ytics.push('"' + label + ' (' + percentages[name] + '%%)" ' + index); 463 | } 464 | ytics.push('"code kind color coding" ' + kY1Offset); 465 | ytics.push('"code kind in execution" ' + (kY1Offset - 1)); 466 | ytics.push('"top ' + kStackFrames + ' js stack frames"' + ' ' + 467 | (kY1Offset - 2)); 468 | ytics.push('"pause times" 0'); 469 | ytics.push('"max deopt size: ' + (max_deopt_size / 1024).toFixed(1) + 470 | ' kB" ' + kDeoptRow); 471 | output("set ytics out nomirror (" + ytics.join(', ') + ")"); 472 | 473 | // Plot timeline. 474 | for (var name in TimerEvents) { 475 | var event = TimerEvents[name]; 476 | var ranges = MergeRanges(event.ranges); 477 | for (var i = 0; i < ranges.length; i++) { 478 | DrawBar(event.index, event.color, 479 | ranges[i].start, ranges[i].end, 480 | kTimerEventWidth); 481 | } 482 | } 483 | 484 | // Plot code kind gathered from ticks. 485 | for (var name in CodeKinds) { 486 | var code_kind = CodeKinds[name]; 487 | var offset = kY1Offset - 1; 488 | // Top most frame. 489 | var row = MergeRanges(TicksToRanges(code_kind.in_execution)); 490 | for (var j = 0; j < row.length; j++) { 491 | DrawBar(offset, code_kind.color, 492 | row[j].start, row[j].end, kExecutionFrameWidth); 493 | } 494 | offset = offset - 2 * kExecutionFrameWidth - kGapWidth; 495 | // Javascript frames. 496 | for (var i = 0; i < kStackFrames; i++) { 497 | offset = offset - 2 * kStackFrameWidth - kGapWidth; 498 | row = MergeRanges(TicksToRanges(code_kind.stack_frames[i])); 499 | for (var j = 0; j < row.length; j++) { 500 | DrawBar(offset, code_kind.color, 501 | row[j].start, row[j].end, kStackFrameWidth); 502 | } 503 | } 504 | } 505 | 506 | // Add labels as legend for code kind colors. 507 | var padding = kCodeKindLabelPadding * (range_end - range_start) / kResX; 508 | var label_x = range_start; 509 | var label_y = kY1Offset; 510 | for (var name in CodeKinds) { 511 | label_x += padding; 512 | output("set label \"" + name + "\" at " + label_x + "," + label_y + 513 | " textcolor rgb \"" + CodeKinds[name].color + "\"" + 514 | " font \"Helvetica,9'\""); 515 | obj_index++; 516 | } 517 | 518 | if (execution_pauses.length == 0) { 519 | // Force plot and return without plotting execution pause impulses. 520 | output("plot 1/0"); 521 | return; 522 | } 523 | 524 | // Label the longest pauses. 525 | execution_pauses = 526 | RestrictRangesTo(execution_pauses, range_start, range_end); 527 | execution_pauses.sort( 528 | function(a, b) { return b.duration() - a.duration(); }); 529 | 530 | var max_pause_time = execution_pauses.length > 0 531 | ? execution_pauses[0].duration() : 0; 532 | padding = kPauseLabelPadding * (range_end - range_start) / kResX; 533 | var y_scale = kY1Offset / max_pause_time / 2; 534 | for (var i = 0; i < execution_pauses.length && i < kNumPauseLabels; i++) { 535 | var pause = execution_pauses[i]; 536 | var label_content = (pause.duration() | 0) + " ms"; 537 | var label_x = pause.end + padding; 538 | var label_y = Math.max(1, (pause.duration() * y_scale)); 539 | output("set label \"" + label_content + "\" at " + 540 | label_x + "," + label_y + " font \"Helvetica,7'\""); 541 | obj_index++; 542 | } 543 | 544 | // Scale second Y-axis appropriately. 545 | var y2range = max_pause_time * num_timer_event / kY1Offset * 2; 546 | output("set y2range [0:" + y2range + "]"); 547 | // Plot graph with impulses as data set. 548 | output("plot '-' using 1:2 axes x1y2 with impulses ls 1"); 549 | for (var i = 0; i < execution_pauses.length; i++) { 550 | var pause = execution_pauses[i]; 551 | output(pause.end + " " + pause.duration()); 552 | obj_index++; 553 | } 554 | output("e"); 555 | return obj_index; 556 | }; 557 | } 558 | 559 | module.exports.PlotScriptComposer = PlotScriptComposer; 560 | -------------------------------------------------------------------------------- /lib/consarray.js: -------------------------------------------------------------------------------- 1 | // Copyright 2009 the V8 project authors. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are 4 | // met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above 9 | // copyright notice, this list of conditions and the following 10 | // disclaimer in the documentation and/or other materials provided 11 | // with the distribution. 12 | // * Neither the name of Google Inc. nor the names of its 13 | // contributors may be used to endorse or promote products derived 14 | // from this software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | 29 | /** 30 | * Constructs a ConsArray object. It is used mainly for tree traversal. 31 | * In this use case we have lots of arrays that we need to iterate 32 | * sequentally. The internal Array implementation is horribly slow 33 | * when concatenating on large (10K items) arrays due to memory copying. 34 | * That's why we avoid copying memory and insead build a linked list 35 | * of arrays to iterate through. 36 | * 37 | * @constructor 38 | */ 39 | function ConsArray() { 40 | this.tail_ = new ConsArray.Cell(null, null); 41 | this.currCell_ = this.tail_; 42 | this.currCellPos_ = 0; 43 | }; 44 | 45 | 46 | /** 47 | * Concatenates another array for iterating. Empty arrays are ignored. 48 | * This operation can be safely performed during ongoing ConsArray 49 | * iteration. 50 | * 51 | * @param {Array} arr Array to concatenate. 52 | */ 53 | ConsArray.prototype.concat = function(arr) { 54 | if (arr.length > 0) { 55 | this.tail_.data = arr; 56 | this.tail_ = this.tail_.next = new ConsArray.Cell(null, null); 57 | } 58 | }; 59 | 60 | 61 | /** 62 | * Whether the end of iteration is reached. 63 | */ 64 | ConsArray.prototype.atEnd = function() { 65 | return this.currCell_ === null || 66 | this.currCell_.data === null || 67 | this.currCellPos_ >= this.currCell_.data.length; 68 | }; 69 | 70 | 71 | /** 72 | * Returns the current item, moves to the next one. 73 | */ 74 | ConsArray.prototype.next = function() { 75 | var result = this.currCell_.data[this.currCellPos_++]; 76 | if (this.currCellPos_ >= this.currCell_.data.length) { 77 | this.currCell_ = this.currCell_.next; 78 | this.currCellPos_ = 0; 79 | } 80 | return result; 81 | }; 82 | 83 | 84 | /** 85 | * A cell object used for constructing a list in ConsArray. 86 | * 87 | * @constructor 88 | */ 89 | ConsArray.Cell = function(data, next) { 90 | this.data = data; 91 | this.next = next; 92 | }; 93 | module.exports = ConsArray; 94 | -------------------------------------------------------------------------------- /lib/csvparser.js: -------------------------------------------------------------------------------- 1 | // Copyright 2009 the V8 project authors. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are 4 | // met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above 9 | // copyright notice, this list of conditions and the following 10 | // disclaimer in the documentation and/or other materials provided 11 | // with the distribution. 12 | // * Neither the name of Google Inc. nor the names of its 13 | // contributors may be used to endorse or promote products derived 14 | // from this software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | 29 | /** 30 | * Creates a CSV lines parser. 31 | */ 32 | function CsvParser() { 33 | }; 34 | 35 | 36 | /** 37 | * A regex for matching a CSV field. 38 | * @private 39 | */ 40 | CsvParser.CSV_FIELD_RE_ = /^"((?:[^"]|"")*)"|([^,]*)/; 41 | 42 | 43 | /** 44 | * A regex for matching a double quote. 45 | * @private 46 | */ 47 | CsvParser.DOUBLE_QUOTE_RE_ = /""/g; 48 | 49 | 50 | /** 51 | * Parses a line of CSV-encoded values. Returns an array of fields. 52 | * 53 | * @param {string} line Input line. 54 | */ 55 | CsvParser.prototype.parseLine = function(line) { 56 | var fieldRe = CsvParser.CSV_FIELD_RE_; 57 | var doubleQuoteRe = CsvParser.DOUBLE_QUOTE_RE_; 58 | var pos = 0; 59 | var endPos = line.length; 60 | var fields = []; 61 | if (endPos > 0) { 62 | do { 63 | var fieldMatch = fieldRe.exec(line.substr(pos)); 64 | if (typeof fieldMatch[1] === "string") { 65 | var field = fieldMatch[1]; 66 | pos += field.length + 3; // Skip comma and quotes. 67 | fields.push(field.replace(doubleQuoteRe, '"')); 68 | } else { 69 | // The second field pattern will match anything, thus 70 | // in the worst case the match will be an empty string. 71 | var field = fieldMatch[2]; 72 | pos += field.length + 1; // Skip comma. 73 | fields.push(field); 74 | } 75 | } while (pos <= endPos); 76 | } 77 | return fields; 78 | }; 79 | module.exports = CsvParser; 80 | -------------------------------------------------------------------------------- /lib/logreader.js: -------------------------------------------------------------------------------- 1 | var CsvParser = require('./csvparser'); 2 | // Copyright 2011 the V8 project authors. All rights reserved. 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following 11 | // disclaimer in the documentation and/or other materials provided 12 | // with the distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived 15 | // from this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | /** 30 | * @fileoverview Log Reader is used to process log file produced by V8. 31 | */ 32 | 33 | 34 | /** 35 | * Base class for processing log files. 36 | * 37 | * @param {Array.} dispatchTable A table used for parsing and processing 38 | * log records. 39 | * @constructor 40 | */ 41 | function LogReader(dispatchTable) { 42 | /** 43 | * @type {Array.} 44 | */ 45 | this.dispatchTable_ = dispatchTable; 46 | 47 | /** 48 | * Current line. 49 | * @type {number} 50 | */ 51 | this.lineNum_ = 0; 52 | 53 | /** 54 | * CSV lines parser. 55 | * @type {CsvParser} 56 | */ 57 | this.csvParser_ = new CsvParser(); 58 | }; 59 | 60 | 61 | /** 62 | * Used for printing error messages. 63 | * 64 | * @param {string} str Error message. 65 | */ 66 | LogReader.prototype.printError = function(str) { 67 | // Do nothing. 68 | }; 69 | 70 | 71 | /** 72 | * Processes a portion of V8 profiler event log. 73 | * 74 | * @param {string} chunk A portion of log. 75 | */ 76 | LogReader.prototype.processLogChunk = function(chunk) { 77 | this.processLog_(chunk.split('\n')); 78 | }; 79 | 80 | 81 | /** 82 | * Processes a line of V8 profiler event log. 83 | * 84 | * @param {string} line A line of log. 85 | */ 86 | LogReader.prototype.processLogLine = function(line) { 87 | this.processLog_([line]); 88 | }; 89 | 90 | 91 | /** 92 | * Processes stack record. 93 | * 94 | * @param {number} pc Program counter. 95 | * @param {number} func JS Function. 96 | * @param {Array.} stack String representation of a stack. 97 | * @return {Array.} Processed stack. 98 | */ 99 | LogReader.prototype.processStack = function(pc, func, stack) { 100 | var fullStack = func ? [pc, func] : [pc]; 101 | var prevFrame = pc; 102 | for (var i = 0, n = stack.length; i < n; ++i) { 103 | var frame = stack[i]; 104 | var firstChar = frame.charAt(0); 105 | if (firstChar == '+' || firstChar == '-') { 106 | // An offset from the previous frame. 107 | prevFrame += parseInt(frame, 16); 108 | fullStack.push(prevFrame); 109 | // Filter out possible 'overflow' string. 110 | } else if (firstChar != 'o') { 111 | fullStack.push(parseInt(frame, 16)); 112 | } 113 | } 114 | return fullStack; 115 | }; 116 | 117 | 118 | /** 119 | * Returns whether a particular dispatch must be skipped. 120 | * 121 | * @param {!Object} dispatch Dispatch record. 122 | * @return {boolean} True if dispatch must be skipped. 123 | */ 124 | LogReader.prototype.skipDispatch = function(dispatch) { 125 | return false; 126 | }; 127 | 128 | 129 | /** 130 | * Does a dispatch of a log record. 131 | * 132 | * @param {Array.} fields Log record. 133 | * @private 134 | */ 135 | LogReader.prototype.dispatchLogRow_ = function(fields) { 136 | // Obtain the dispatch. 137 | var command = fields[0]; 138 | if (!(command in this.dispatchTable_)) return; 139 | 140 | var dispatch = this.dispatchTable_[command]; 141 | 142 | if (dispatch === null || this.skipDispatch(dispatch)) { 143 | return; 144 | } 145 | 146 | // special case so we can parse v8.log from both 0.10 and 0.11 147 | // after first 'code-creation' with less than 5 fields we assume that it's from 148 | // v8 3.14.5.9 and 'kind' field is missing from second position 149 | // ( present in v8 3.26.33 profile, node v0.11+ ) 150 | if (command == 'code-creation' && fields.length <= 5) { 151 | if (typeof dispatch.codeCreationKind == "undefined") { 152 | dispatch.codeCreationKind = false; 153 | dispatch.parsers = [null, 'void', parseInt, parseInt, null, 'var-args']; 154 | } 155 | } 156 | 157 | // Parse fields. 158 | var parsedFields = []; 159 | var fieldIndex = 1; 160 | var parserIndex = 0; 161 | var parser; 162 | 163 | for ( ;parserIndex < dispatch.parsers.length; ++fieldIndex, ++parserIndex) { 164 | parser = dispatch.parsers[parserIndex]; 165 | if (parser === null) { 166 | parsedFields.push(fields[fieldIndex]); 167 | } else if (typeof parser == 'function') { 168 | parsedFields.push(parser(fields[fieldIndex])); 169 | } else if (parser === 'void') { 170 | // add undefined to parameters, but don't consume field 171 | parsedFields.push(void(0)); 172 | --fieldIndex; 173 | } else { 174 | // var-args 175 | parsedFields.push(fields.slice(fieldIndex)); 176 | break; 177 | } 178 | } 179 | 180 | // Run the processor. 181 | dispatch.processor.apply(this, parsedFields); 182 | }; 183 | 184 | 185 | /** 186 | * Processes log lines. 187 | * 188 | * @param {Array.} lines Log lines. 189 | * @private 190 | */ 191 | LogReader.prototype.processLog_ = function(lines) { 192 | for (var i = 0, n = lines.length; i < n; ++i, ++this.lineNum_) { 193 | var line = lines[i]; 194 | if (!line) { 195 | continue; 196 | } 197 | try { 198 | var fields = this.csvParser_.parseLine(line); 199 | this.dispatchLogRow_(fields); 200 | } catch (e) { 201 | this.printError('line ' + (this.lineNum_ + 1) + ': ' + (e.message || e)); 202 | } 203 | } 204 | }; 205 | module.exports.LogReader = LogReader; 206 | -------------------------------------------------------------------------------- /lib/profile.js: -------------------------------------------------------------------------------- 1 | // Copyright 2009 the V8 project authors. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are 4 | // met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above 9 | // copyright notice, this list of conditions and the following 10 | // disclaimer in the documentation and/or other materials provided 11 | // with the distribution. 12 | // * Neither the name of Google Inc. nor the names of its 13 | // contributors may be used to endorse or promote products derived 14 | // from this software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | var codeMapModule = require('./codemap'); 29 | var CodeMap = codeMapModule.CodeMap; 30 | var ConsArray = require('./consarray'); 31 | 32 | /** 33 | * Creates a profile object for processing profiling-related events 34 | * and calculating function execution times. 35 | * 36 | * @constructor 37 | */ 38 | function Profile() { 39 | this.codeMap_ = new CodeMap(); 40 | this.topDownTree_ = new CallTree(); 41 | this.bottomUpTree_ = new CallTree(); 42 | }; 43 | 44 | 45 | /** 46 | * Returns whether a function with the specified name must be skipped. 47 | * Should be overriden by subclasses. 48 | * 49 | * @param {string} name Function name. 50 | */ 51 | Profile.prototype.skipThisFunction = function(name) { 52 | return false; 53 | }; 54 | 55 | 56 | /** 57 | * Enum for profiler operations that involve looking up existing 58 | * code entries. 59 | * 60 | * @enum {number} 61 | */ 62 | Profile.Operation = { 63 | MOVE: 0, 64 | DELETE: 1, 65 | TICK: 2 66 | }; 67 | 68 | 69 | /** 70 | * Enum for code state regarding its dynamic optimization. 71 | * 72 | * @enum {number} 73 | */ 74 | Profile.CodeState = { 75 | COMPILED: 0, 76 | OPTIMIZABLE: 1, 77 | OPTIMIZED: 2 78 | }; 79 | 80 | 81 | /** 82 | * Called whenever the specified operation has failed finding a function 83 | * containing the specified address. Should be overriden by subclasses. 84 | * See the Profile.Operation enum for the list of 85 | * possible operations. 86 | * 87 | * @param {number} operation Operation. 88 | * @param {number} addr Address of the unknown code. 89 | * @param {number} opt_stackPos If an unknown address is encountered 90 | * during stack strace processing, specifies a position of the frame 91 | * containing the address. 92 | */ 93 | Profile.prototype.handleUnknownCode = function( 94 | operation, addr, opt_stackPos) { 95 | }; 96 | 97 | 98 | /** 99 | * Registers a library. 100 | * 101 | * @param {string} name Code entry name. 102 | * @param {number} startAddr Starting address. 103 | * @param {number} endAddr Ending address. 104 | */ 105 | Profile.prototype.addLibrary = function( 106 | name, startAddr, endAddr) { 107 | var entry = new CodeMap.CodeEntry( 108 | endAddr - startAddr, name); 109 | this.codeMap_.addLibrary(startAddr, entry); 110 | return entry; 111 | }; 112 | 113 | 114 | /** 115 | * Registers statically compiled code entry. 116 | * 117 | * @param {string} name Code entry name. 118 | * @param {number} startAddr Starting address. 119 | * @param {number} endAddr Ending address. 120 | */ 121 | Profile.prototype.addStaticCode = function( 122 | name, startAddr, endAddr) { 123 | var entry = new CodeMap.CodeEntry( 124 | endAddr - startAddr, name); 125 | this.codeMap_.addStaticCode(startAddr, entry); 126 | return entry; 127 | }; 128 | 129 | 130 | /** 131 | * Registers dynamic (JIT-compiled) code entry. 132 | * 133 | * @param {string} type Code entry type. 134 | * @param {string} name Code entry name. 135 | * @param {number} start Starting address. 136 | * @param {number} size Code entry size. 137 | */ 138 | Profile.prototype.addCode = function( 139 | type, name, start, size) { 140 | var entry = new Profile.DynamicCodeEntry(size, type, name); 141 | this.codeMap_.addCode(start, entry); 142 | return entry; 143 | }; 144 | 145 | 146 | /** 147 | * Registers dynamic (JIT-compiled) code entry. 148 | * 149 | * @param {string} type Code entry type. 150 | * @param {string} name Code entry name. 151 | * @param {number} start Starting address. 152 | * @param {number} size Code entry size. 153 | * @param {number} funcAddr Shared function object address. 154 | * @param {Profile.CodeState} state Optimization state. 155 | */ 156 | Profile.prototype.addFuncCode = function( 157 | type, name, start, size, funcAddr, state) { 158 | // As code and functions are in the same address space, 159 | // it is safe to put them in a single code map. 160 | var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr); 161 | if (!func) { 162 | func = new Profile.FunctionEntry(name); 163 | this.codeMap_.addCode(funcAddr, func); 164 | } else if (func.name !== name) { 165 | // Function object has been overwritten with a new one. 166 | func.name = name; 167 | } 168 | var entry = this.codeMap_.findDynamicEntryByStartAddress(start); 169 | if (entry) { 170 | if (entry.size === size && entry.func === func) { 171 | // Entry state has changed. 172 | entry.state = state; 173 | } 174 | } else { 175 | entry = new Profile.DynamicFuncCodeEntry(size, type, func, state); 176 | this.codeMap_.addCode(start, entry); 177 | } 178 | return entry; 179 | }; 180 | 181 | 182 | /** 183 | * Reports about moving of a dynamic code entry. 184 | * 185 | * @param {number} from Current code entry address. 186 | * @param {number} to New code entry address. 187 | */ 188 | Profile.prototype.moveCode = function(from, to) { 189 | try { 190 | this.codeMap_.moveCode(from, to); 191 | } catch (e) { 192 | this.handleUnknownCode(Profile.Operation.MOVE, from); 193 | } 194 | }; 195 | 196 | 197 | /** 198 | * Reports about deletion of a dynamic code entry. 199 | * 200 | * @param {number} start Starting address. 201 | */ 202 | Profile.prototype.deleteCode = function(start) { 203 | try { 204 | this.codeMap_.deleteCode(start); 205 | } catch (e) { 206 | this.handleUnknownCode(Profile.Operation.DELETE, start); 207 | } 208 | }; 209 | 210 | 211 | /** 212 | * Reports about moving of a dynamic code entry. 213 | * 214 | * @param {number} from Current code entry address. 215 | * @param {number} to New code entry address. 216 | */ 217 | Profile.prototype.moveFunc = function(from, to) { 218 | if (this.codeMap_.findDynamicEntryByStartAddress(from)) { 219 | this.codeMap_.moveCode(from, to); 220 | } 221 | }; 222 | 223 | 224 | /** 225 | * Retrieves a code entry by an address. 226 | * 227 | * @param {number} addr Entry address. 228 | */ 229 | Profile.prototype.findEntry = function(addr) { 230 | return this.codeMap_.findEntry(addr); 231 | }; 232 | 233 | 234 | /** 235 | * Records a tick event. Stack must contain a sequence of 236 | * addresses starting with the program counter value. 237 | * 238 | * @param {Array} stack Stack sample. 239 | */ 240 | Profile.prototype.recordTick = function(stack) { 241 | var processedStack = this.resolveAndFilterFuncs_(stack); 242 | this.bottomUpTree_.addPath(processedStack); 243 | processedStack.reverse(); 244 | this.topDownTree_.addPath(processedStack); 245 | }; 246 | 247 | 248 | /** 249 | * Translates addresses into function names and filters unneeded 250 | * functions. 251 | * 252 | * @param {Array} stack Stack sample. 253 | */ 254 | Profile.prototype.resolveAndFilterFuncs_ = function(stack) { 255 | var result = []; 256 | for (var i = 0; i < stack.length; ++i) { 257 | var entry = this.codeMap_.findEntry(stack[i]); 258 | if (entry) { 259 | var name = entry.getName(); 260 | if (!this.skipThisFunction(name)) { 261 | result.push(name); 262 | } 263 | } else { 264 | this.handleUnknownCode( 265 | Profile.Operation.TICK, stack[i], i); 266 | } 267 | } 268 | return result; 269 | }; 270 | 271 | 272 | /** 273 | * Performs a BF traversal of the top down call graph. 274 | * 275 | * @param {function(CallTree.Node)} f Visitor function. 276 | */ 277 | Profile.prototype.traverseTopDownTree = function(f) { 278 | this.topDownTree_.traverse(f); 279 | }; 280 | 281 | 282 | /** 283 | * Performs a BF traversal of the bottom up call graph. 284 | * 285 | * @param {function(CallTree.Node)} f Visitor function. 286 | */ 287 | Profile.prototype.traverseBottomUpTree = function(f) { 288 | this.bottomUpTree_.traverse(f); 289 | }; 290 | 291 | 292 | /** 293 | * Calculates a top down profile for a node with the specified label. 294 | * If no name specified, returns the whole top down calls tree. 295 | * 296 | * @param {string} opt_label Node label. 297 | */ 298 | Profile.prototype.getTopDownProfile = function(opt_label) { 299 | return this.getTreeProfile_(this.topDownTree_, opt_label); 300 | }; 301 | 302 | 303 | /** 304 | * Calculates a bottom up profile for a node with the specified label. 305 | * If no name specified, returns the whole bottom up calls tree. 306 | * 307 | * @param {string} opt_label Node label. 308 | */ 309 | Profile.prototype.getBottomUpProfile = function(opt_label) { 310 | return this.getTreeProfile_(this.bottomUpTree_, opt_label); 311 | }; 312 | 313 | 314 | /** 315 | * Helper function for calculating a tree profile. 316 | * 317 | * @param {Profile.CallTree} tree Call tree. 318 | * @param {string} opt_label Node label. 319 | */ 320 | Profile.prototype.getTreeProfile_ = function(tree, opt_label) { 321 | if (!opt_label) { 322 | tree.computeTotalWeights(); 323 | return tree; 324 | } else { 325 | var subTree = tree.cloneSubtree(opt_label); 326 | subTree.computeTotalWeights(); 327 | return subTree; 328 | } 329 | }; 330 | 331 | 332 | /** 333 | * Calculates a flat profile of callees starting from a node with 334 | * the specified label. If no name specified, starts from the root. 335 | * 336 | * @param {string} opt_label Starting node label. 337 | */ 338 | Profile.prototype.getFlatProfile = function(opt_label) { 339 | var counters = new CallTree(); 340 | var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL; 341 | var precs = {}; 342 | precs[rootLabel] = 0; 343 | var root = counters.findOrAddChild(rootLabel); 344 | 345 | this.topDownTree_.computeTotalWeights(); 346 | this.topDownTree_.traverseInDepth( 347 | function onEnter(node) { 348 | if (!(node.label in precs)) { 349 | precs[node.label] = 0; 350 | } 351 | var nodeLabelIsRootLabel = node.label == rootLabel; 352 | if (nodeLabelIsRootLabel || precs[rootLabel] > 0) { 353 | if (precs[rootLabel] == 0) { 354 | root.selfWeight += node.selfWeight; 355 | root.totalWeight += node.totalWeight; 356 | } else { 357 | var rec = root.findOrAddChild(node.label); 358 | rec.selfWeight += node.selfWeight; 359 | if (nodeLabelIsRootLabel || precs[node.label] == 0) { 360 | rec.totalWeight += node.totalWeight; 361 | } 362 | } 363 | precs[node.label]++; 364 | } 365 | }, 366 | function onExit(node) { 367 | if (node.label == rootLabel || precs[rootLabel] > 0) { 368 | precs[node.label]--; 369 | } 370 | }, 371 | null); 372 | 373 | if (!opt_label) { 374 | // If we have created a flat profile for the whole program, we don't 375 | // need an explicit root in it. Thus, replace the counters tree 376 | // root with the node corresponding to the whole program. 377 | counters.root_ = root; 378 | } else { 379 | // Propagate weights so percents can be calculated correctly. 380 | counters.getRoot().selfWeight = root.selfWeight; 381 | counters.getRoot().totalWeight = root.totalWeight; 382 | } 383 | return counters; 384 | }; 385 | 386 | 387 | /** 388 | * Cleans up function entries that are not referenced by code entries. 389 | */ 390 | Profile.prototype.cleanUpFuncEntries = function() { 391 | var referencedFuncEntries = []; 392 | var entries = this.codeMap_.getAllDynamicEntriesWithAddresses(); 393 | for (var i = 0, l = entries.length; i < l; ++i) { 394 | if (entries[i][1].constructor === Profile.FunctionEntry) { 395 | entries[i][1].used = false; 396 | } 397 | } 398 | for (var i = 0, l = entries.length; i < l; ++i) { 399 | if ("func" in entries[i][1]) { 400 | entries[i][1].func.used = true; 401 | } 402 | } 403 | for (var i = 0, l = entries.length; i < l; ++i) { 404 | if (entries[i][1].constructor === Profile.FunctionEntry && 405 | !entries[i][1].used) { 406 | this.codeMap_.deleteCode(entries[i][0]); 407 | } 408 | } 409 | }; 410 | 411 | 412 | /** 413 | * Creates a dynamic code entry. 414 | * 415 | * @param {number} size Code size. 416 | * @param {string} type Code type. 417 | * @param {string} name Function name. 418 | * @constructor 419 | */ 420 | Profile.DynamicCodeEntry = function(size, type, name) { 421 | CodeMap.CodeEntry.call(this, size, name); 422 | this.type = type; 423 | }; 424 | 425 | 426 | /** 427 | * Returns node name. 428 | */ 429 | Profile.DynamicCodeEntry.prototype.getName = function() { 430 | return this.type + ': ' + this.name; 431 | }; 432 | 433 | 434 | /** 435 | * Returns raw node name (without type decoration). 436 | */ 437 | Profile.DynamicCodeEntry.prototype.getRawName = function() { 438 | return this.name; 439 | }; 440 | 441 | 442 | Profile.DynamicCodeEntry.prototype.isJSFunction = function() { 443 | return false; 444 | }; 445 | 446 | 447 | Profile.DynamicCodeEntry.prototype.toString = function() { 448 | return this.getName() + ': ' + this.size.toString(16); 449 | }; 450 | 451 | 452 | /** 453 | * Creates a dynamic code entry. 454 | * 455 | * @param {number} size Code size. 456 | * @param {string} type Code type. 457 | * @param {Profile.FunctionEntry} func Shared function entry. 458 | * @param {Profile.CodeState} state Code optimization state. 459 | * @constructor 460 | */ 461 | Profile.DynamicFuncCodeEntry = function(size, type, func, state) { 462 | CodeMap.CodeEntry.call(this, size); 463 | this.type = type; 464 | this.func = func; 465 | this.state = state; 466 | }; 467 | 468 | Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"]; 469 | 470 | /** 471 | * Returns node name. 472 | */ 473 | Profile.DynamicFuncCodeEntry.prototype.getName = function() { 474 | var name = this.func.getName(); 475 | return this.type + ': ' + Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + name; 476 | }; 477 | 478 | 479 | /** 480 | * Returns raw node name (without type decoration). 481 | */ 482 | Profile.DynamicFuncCodeEntry.prototype.getRawName = function() { 483 | return this.func.getName(); 484 | }; 485 | 486 | 487 | Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() { 488 | return true; 489 | }; 490 | 491 | 492 | Profile.DynamicFuncCodeEntry.prototype.toString = function() { 493 | return this.getName() + ': ' + this.size.toString(16); 494 | }; 495 | 496 | 497 | /** 498 | * Creates a shared function object entry. 499 | * 500 | * @param {string} name Function name. 501 | * @constructor 502 | */ 503 | Profile.FunctionEntry = function(name) { 504 | CodeMap.CodeEntry.call(this, 0, name); 505 | }; 506 | 507 | 508 | /** 509 | * Returns node name. 510 | */ 511 | Profile.FunctionEntry.prototype.getName = function() { 512 | var name = this.name; 513 | if (name.length == 0) { 514 | name = ''; 515 | } else if (name.charAt(0) == ' ') { 516 | // An anonymous function with location: " aaa.js:10". 517 | name = '' + name; 518 | } 519 | return name; 520 | }; 521 | 522 | Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString; 523 | 524 | /** 525 | * Constructs a call graph. 526 | * 527 | * @constructor 528 | */ 529 | function CallTree() { 530 | this.root_ = new CallTree.Node( 531 | CallTree.ROOT_NODE_LABEL); 532 | }; 533 | 534 | 535 | /** 536 | * The label of the root node. 537 | */ 538 | CallTree.ROOT_NODE_LABEL = ''; 539 | 540 | 541 | /** 542 | * @private 543 | */ 544 | CallTree.prototype.totalsComputed_ = false; 545 | 546 | 547 | /** 548 | * Returns the tree root. 549 | */ 550 | CallTree.prototype.getRoot = function() { 551 | return this.root_; 552 | }; 553 | 554 | 555 | /** 556 | * Adds the specified call path, constructing nodes as necessary. 557 | * 558 | * @param {Array} path Call path. 559 | */ 560 | CallTree.prototype.addPath = function(path) { 561 | if (path.length == 0) { 562 | return; 563 | } 564 | var curr = this.root_; 565 | for (var i = 0; i < path.length; ++i) { 566 | curr = curr.findOrAddChild(path[i]); 567 | } 568 | curr.selfWeight++; 569 | this.totalsComputed_ = false; 570 | }; 571 | 572 | 573 | /** 574 | * Finds an immediate child of the specified parent with the specified 575 | * label, creates a child node if necessary. If a parent node isn't 576 | * specified, uses tree root. 577 | * 578 | * @param {string} label Child node label. 579 | */ 580 | CallTree.prototype.findOrAddChild = function(label) { 581 | return this.root_.findOrAddChild(label); 582 | }; 583 | 584 | 585 | /** 586 | * Creates a subtree by cloning and merging all subtrees rooted at nodes 587 | * with a given label. E.g. cloning the following call tree on label 'A' 588 | * will give the following result: 589 | * 590 | * -- 591 | * / / 592 | * == clone on 'A' ==> -- 593 | * \ \ 594 | * ---- 595 | * 596 | * And 's selfWeight will be the sum of selfWeights of 's from the 597 | * source call tree. 598 | * 599 | * @param {string} label The label of the new root node. 600 | */ 601 | CallTree.prototype.cloneSubtree = function(label) { 602 | var subTree = new CallTree(); 603 | this.traverse(function(node, parent) { 604 | if (!parent && node.label != label) { 605 | return null; 606 | } 607 | var child = (parent ? parent : subTree).findOrAddChild(node.label); 608 | child.selfWeight += node.selfWeight; 609 | return child; 610 | }); 611 | return subTree; 612 | }; 613 | 614 | 615 | /** 616 | * Computes total weights in the call graph. 617 | */ 618 | CallTree.prototype.computeTotalWeights = function() { 619 | if (this.totalsComputed_) { 620 | return; 621 | } 622 | this.root_.computeTotalWeight(); 623 | this.totalsComputed_ = true; 624 | }; 625 | 626 | 627 | /** 628 | * Traverses the call graph in preorder. This function can be used for 629 | * building optionally modified tree clones. This is the boilerplate code 630 | * for this scenario: 631 | * 632 | * callTree.traverse(function(node, parentClone) { 633 | * var nodeClone = cloneNode(node); 634 | * if (parentClone) 635 | * parentClone.addChild(nodeClone); 636 | * return nodeClone; 637 | * }); 638 | * 639 | * @param {function(CallTree.Node, *)} f Visitor function. 640 | * The second parameter is the result of calling 'f' on the parent node. 641 | */ 642 | CallTree.prototype.traverse = function(f) { 643 | var pairsToProcess = new ConsArray(); 644 | pairsToProcess.concat([{node: this.root_, param: null}]); 645 | while (!pairsToProcess.atEnd()) { 646 | var pair = pairsToProcess.next(); 647 | var node = pair.node; 648 | var newParam = f(node, pair.param); 649 | var morePairsToProcess = []; 650 | node.forEachChild(function (child) { 651 | morePairsToProcess.push({node: child, param: newParam}); }); 652 | pairsToProcess.concat(morePairsToProcess); 653 | } 654 | }; 655 | 656 | 657 | /** 658 | * Performs an indepth call graph traversal. 659 | * 660 | * @param {function(CallTree.Node)} enter A function called 661 | * prior to visiting node's children. 662 | * @param {function(CallTree.Node)} exit A function called 663 | * after visiting node's children. 664 | */ 665 | CallTree.prototype.traverseInDepth = function(enter, exit) { 666 | function traverse(node) { 667 | enter(node); 668 | node.forEachChild(traverse); 669 | exit(node); 670 | } 671 | traverse(this.root_); 672 | }; 673 | 674 | 675 | /** 676 | * Constructs a call graph node. 677 | * 678 | * @param {string} label Node label. 679 | * @param {CallTree.Node} opt_parent Node parent. 680 | */ 681 | CallTree.Node = function(label, opt_parent) { 682 | this.label = label; 683 | this.parent = opt_parent; 684 | this.children = {}; 685 | }; 686 | 687 | 688 | /** 689 | * Node self weight (how many times this node was the last node in 690 | * a call path). 691 | * @type {number} 692 | */ 693 | CallTree.Node.prototype.selfWeight = 0; 694 | 695 | 696 | /** 697 | * Node total weight (includes weights of all children). 698 | * @type {number} 699 | */ 700 | CallTree.Node.prototype.totalWeight = 0; 701 | 702 | 703 | /** 704 | * Adds a child node. 705 | * 706 | * @param {string} label Child node label. 707 | */ 708 | CallTree.Node.prototype.addChild = function(label) { 709 | var child = new CallTree.Node(label, this); 710 | this.children[label] = child; 711 | return child; 712 | }; 713 | 714 | 715 | /** 716 | * Computes node's total weight. 717 | */ 718 | CallTree.Node.prototype.computeTotalWeight = 719 | function() { 720 | var totalWeight = this.selfWeight; 721 | this.forEachChild(function(child) { 722 | totalWeight += child.computeTotalWeight(); }); 723 | return this.totalWeight = totalWeight; 724 | }; 725 | 726 | 727 | /** 728 | * Returns all node's children as an array. 729 | */ 730 | CallTree.Node.prototype.exportChildren = function() { 731 | var result = []; 732 | this.forEachChild(function (node) { result.push(node); }); 733 | return result; 734 | }; 735 | 736 | 737 | /** 738 | * Finds an immediate child with the specified label. 739 | * 740 | * @param {string} label Child node label. 741 | */ 742 | CallTree.Node.prototype.findChild = function(label) { 743 | return this.children[label] || null; 744 | }; 745 | 746 | 747 | /** 748 | * Finds an immediate child with the specified label, creates a child 749 | * node if necessary. 750 | * 751 | * @param {string} label Child node label. 752 | */ 753 | CallTree.Node.prototype.findOrAddChild = function(label) { 754 | return this.findChild(label) || this.addChild(label); 755 | }; 756 | 757 | 758 | /** 759 | * Calls the specified function for every child. 760 | * 761 | * @param {function(CallTree.Node)} f Visitor function. 762 | */ 763 | CallTree.Node.prototype.forEachChild = function(f) { 764 | for (var c in this.children) { 765 | f(this.children[c]); 766 | } 767 | }; 768 | 769 | 770 | /** 771 | * Walks up from the current node up to the call tree root. 772 | * 773 | * @param {function(CallTree.Node)} f Visitor function. 774 | */ 775 | CallTree.Node.prototype.walkUpToRoot = function(f) { 776 | for (var curr = this; curr != null; curr = curr.parent) { 777 | f(curr); 778 | } 779 | }; 780 | 781 | 782 | /** 783 | * Tries to find a node with the specified path. 784 | * 785 | * @param {Array} labels The path. 786 | * @param {function(CallTree.Node)} opt_f Visitor function. 787 | */ 788 | CallTree.Node.prototype.descendToChild = function( 789 | labels, opt_f) { 790 | for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) { 791 | var child = curr.findChild(labels[pos]); 792 | if (opt_f) { 793 | opt_f(child, pos); 794 | } 795 | curr = child; 796 | } 797 | return curr; 798 | }; 799 | 800 | module.exports.Profile = Profile; 801 | -------------------------------------------------------------------------------- /lib/profile_view.js: -------------------------------------------------------------------------------- 1 | // Copyright 2009 the V8 project authors. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are 4 | // met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above 9 | // copyright notice, this list of conditions and the following 10 | // disclaimer in the documentation and/or other materials provided 11 | // with the distribution. 12 | // * Neither the name of Google Inc. nor the names of its 13 | // contributors may be used to endorse or promote products derived 14 | // from this software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | var ConsArray = require('./consarray'); 29 | 30 | /** 31 | * Creates a Profile View builder object. 32 | * 33 | * @param {number} samplingRate Number of ms between profiler ticks. 34 | * @constructor 35 | */ 36 | function ViewBuilder(samplingRate) { 37 | this.samplingRate = samplingRate; 38 | }; 39 | 40 | 41 | /** 42 | * Builds a profile view for the specified call tree. 43 | * 44 | * @param {CallTree} callTree A call tree. 45 | * @param {boolean} opt_bottomUpViewWeights Whether remapping 46 | * of self weights for a bottom up view is needed. 47 | */ 48 | ViewBuilder.prototype.buildView = function( 49 | callTree, opt_bottomUpViewWeights) { 50 | var head; 51 | var samplingRate = this.samplingRate; 52 | var createViewNode = this.createViewNode; 53 | callTree.traverse(function(node, viewParent) { 54 | var totalWeight = node.totalWeight * samplingRate; 55 | var selfWeight = node.selfWeight * samplingRate; 56 | if (opt_bottomUpViewWeights === true) { 57 | if (viewParent === head) { 58 | selfWeight = totalWeight; 59 | } else { 60 | selfWeight = 0; 61 | } 62 | } 63 | var viewNode = createViewNode(node.label, totalWeight, selfWeight, head); 64 | if (viewParent) { 65 | viewParent.addChild(viewNode); 66 | } else { 67 | head = viewNode; 68 | } 69 | return viewNode; 70 | }); 71 | var view = this.createView(head); 72 | return view; 73 | }; 74 | 75 | 76 | /** 77 | * Factory method for a profile view. 78 | * 79 | * @param {ProfileView.Node} head View head node. 80 | * @return {ProfileView} Profile view. 81 | */ 82 | ViewBuilder.prototype.createView = function(head) { 83 | return new ProfileView(head); 84 | }; 85 | 86 | 87 | /** 88 | * Factory method for a profile view node. 89 | * 90 | * @param {string} internalFuncName A fully qualified function name. 91 | * @param {number} totalTime Amount of time that application spent in the 92 | * corresponding function and its descendants (not that depending on 93 | * profile they can be either callees or callers.) 94 | * @param {number} selfTime Amount of time that application spent in the 95 | * corresponding function only. 96 | * @param {ProfileView.Node} head Profile view head. 97 | * @return {ProfileView.Node} Profile view node. 98 | */ 99 | ViewBuilder.prototype.createViewNode = function( 100 | funcName, totalTime, selfTime, head) { 101 | return new ProfileView.Node( 102 | funcName, totalTime, selfTime, head); 103 | }; 104 | 105 | 106 | /** 107 | * Creates a Profile View object. It allows to perform sorting 108 | * and filtering actions on the profile. 109 | * 110 | * @param {ProfileView.Node} head Head (root) node. 111 | * @constructor 112 | */ 113 | function ProfileView(head) { 114 | this.head = head; 115 | }; 116 | 117 | 118 | /** 119 | * Sorts the profile view using the specified sort function. 120 | * 121 | * @param {function(ProfileView.Node, 122 | * ProfileView.Node):number} sortFunc A sorting 123 | * functions. Must comply with Array.sort sorting function requirements. 124 | */ 125 | ProfileView.prototype.sort = function(sortFunc) { 126 | this.traverse(function (node) { 127 | node.sortChildren(sortFunc); 128 | }); 129 | }; 130 | 131 | 132 | /** 133 | * Traverses profile view nodes in preorder. 134 | * 135 | * @param {function(ProfileView.Node)} f Visitor function. 136 | */ 137 | ProfileView.prototype.traverse = function(f) { 138 | var nodesToTraverse = new ConsArray(); 139 | nodesToTraverse.concat([this.head]); 140 | while (!nodesToTraverse.atEnd()) { 141 | var node = nodesToTraverse.next(); 142 | f(node); 143 | nodesToTraverse.concat(node.children); 144 | } 145 | }; 146 | 147 | 148 | /** 149 | * Constructs a Profile View node object. Each node object corresponds to 150 | * a function call. 151 | * 152 | * @param {string} internalFuncName A fully qualified function name. 153 | * @param {number} totalTime Amount of time that application spent in the 154 | * corresponding function and its descendants (not that depending on 155 | * profile they can be either callees or callers.) 156 | * @param {number} selfTime Amount of time that application spent in the 157 | * corresponding function only. 158 | * @param {ProfileView.Node} head Profile view head. 159 | * @constructor 160 | */ 161 | ProfileView.Node = function( 162 | internalFuncName, totalTime, selfTime, head) { 163 | this.internalFuncName = internalFuncName; 164 | this.totalTime = totalTime; 165 | this.selfTime = selfTime; 166 | this.head = head; 167 | this.parent = null; 168 | this.children = []; 169 | }; 170 | 171 | 172 | /** 173 | * Returns a share of the function's total time in application's total time. 174 | */ 175 | ProfileView.Node.prototype.__defineGetter__( 176 | 'totalPercent', 177 | function() { return this.totalTime / 178 | (this.head ? this.head.totalTime : this.totalTime) * 100.0; }); 179 | 180 | 181 | /** 182 | * Returns a share of the function's self time in application's total time. 183 | */ 184 | ProfileView.Node.prototype.__defineGetter__( 185 | 'selfPercent', 186 | function() { return this.selfTime / 187 | (this.head ? this.head.totalTime : this.totalTime) * 100.0; }); 188 | 189 | 190 | /** 191 | * Returns a share of the function's total time in its parent's total time. 192 | */ 193 | ProfileView.Node.prototype.__defineGetter__( 194 | 'parentTotalPercent', 195 | function() { return this.totalTime / 196 | (this.parent ? this.parent.totalTime : this.totalTime) * 100.0; }); 197 | 198 | 199 | /** 200 | * Adds a child to the node. 201 | * 202 | * @param {ProfileView.Node} node Child node. 203 | */ 204 | ProfileView.Node.prototype.addChild = function(node) { 205 | node.parent = this; 206 | this.children.push(node); 207 | }; 208 | 209 | 210 | /** 211 | * Sorts all the node's children recursively. 212 | * 213 | * @param {function(ProfileView.Node, 214 | * ProfileView.Node):number} sortFunc A sorting 215 | * functions. Must comply with Array.sort sorting function requirements. 216 | */ 217 | ProfileView.Node.prototype.sortChildren = function( 218 | sortFunc) { 219 | this.children.sort(sortFunc); 220 | }; 221 | 222 | module.exports = ProfileView; 223 | module.exports.ViewBuilder = ViewBuilder; 224 | -------------------------------------------------------------------------------- /lib/splaytree.js: -------------------------------------------------------------------------------- 1 | // Copyright 2009 the V8 project authors. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are 4 | // met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above 9 | // copyright notice, this list of conditions and the following 10 | // disclaimer in the documentation and/or other materials provided 11 | // with the distribution. 12 | // * Neither the name of Google Inc. nor the names of its 13 | // contributors may be used to endorse or promote products derived 14 | // from this software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | 29 | /** 30 | * Constructs a Splay tree. A splay tree is a self-balancing binary 31 | * search tree with the additional property that recently accessed 32 | * elements are quick to access again. It performs basic operations 33 | * such as insertion, look-up and removal in O(log(n)) amortized time. 34 | * 35 | * @constructor 36 | */ 37 | function SplayTree() { 38 | }; 39 | 40 | 41 | /** 42 | * Pointer to the root node of the tree. 43 | * 44 | * @type {SplayTree.Node} 45 | * @private 46 | */ 47 | SplayTree.prototype.root_ = null; 48 | 49 | 50 | /** 51 | * @return {boolean} Whether the tree is empty. 52 | */ 53 | SplayTree.prototype.isEmpty = function() { 54 | return !this.root_; 55 | }; 56 | 57 | 58 | 59 | /** 60 | * Inserts a node into the tree with the specified key and value if 61 | * the tree does not already contain a node with the specified key. If 62 | * the value is inserted, it becomes the root of the tree. 63 | * 64 | * @param {number} key Key to insert into the tree. 65 | * @param {*} value Value to insert into the tree. 66 | */ 67 | SplayTree.prototype.insert = function(key, value) { 68 | if (this.isEmpty()) { 69 | this.root_ = new SplayTree.Node(key, value); 70 | return; 71 | } 72 | // Splay on the key to move the last node on the search path for 73 | // the key to the root of the tree. 74 | this.splay_(key); 75 | if (this.root_.key == key) { 76 | return; 77 | } 78 | var node = new SplayTree.Node(key, value); 79 | if (key > this.root_.key) { 80 | node.left = this.root_; 81 | node.right = this.root_.right; 82 | this.root_.right = null; 83 | } else { 84 | node.right = this.root_; 85 | node.left = this.root_.left; 86 | this.root_.left = null; 87 | } 88 | this.root_ = node; 89 | }; 90 | 91 | 92 | /** 93 | * Removes a node with the specified key from the tree if the tree 94 | * contains a node with this key. The removed node is returned. If the 95 | * key is not found, an exception is thrown. 96 | * 97 | * @param {number} key Key to find and remove from the tree. 98 | * @return {SplayTree.Node} The removed node. 99 | */ 100 | SplayTree.prototype.remove = function(key) { 101 | if (this.isEmpty()) { 102 | throw Error('Key not found: ' + key); 103 | } 104 | this.splay_(key); 105 | if (this.root_.key != key) { 106 | throw Error('Key not found: ' + key); 107 | } 108 | var removed = this.root_; 109 | if (!this.root_.left) { 110 | this.root_ = this.root_.right; 111 | } else { 112 | var right = this.root_.right; 113 | this.root_ = this.root_.left; 114 | // Splay to make sure that the new root has an empty right child. 115 | this.splay_(key); 116 | // Insert the original right child as the right child of the new 117 | // root. 118 | this.root_.right = right; 119 | } 120 | return removed; 121 | }; 122 | 123 | 124 | /** 125 | * Returns the node having the specified key or null if the tree doesn't contain 126 | * a node with the specified key. 127 | * 128 | * @param {number} key Key to find in the tree. 129 | * @return {SplayTree.Node} Node having the specified key. 130 | */ 131 | SplayTree.prototype.find = function(key) { 132 | if (this.isEmpty()) { 133 | return null; 134 | } 135 | this.splay_(key); 136 | return this.root_.key == key ? this.root_ : null; 137 | }; 138 | 139 | 140 | /** 141 | * @return {SplayTree.Node} Node having the minimum key value. 142 | */ 143 | SplayTree.prototype.findMin = function() { 144 | if (this.isEmpty()) { 145 | return null; 146 | } 147 | var current = this.root_; 148 | while (current.left) { 149 | current = current.left; 150 | } 151 | return current; 152 | }; 153 | 154 | 155 | /** 156 | * @return {SplayTree.Node} Node having the maximum key value. 157 | */ 158 | SplayTree.prototype.findMax = function(opt_startNode) { 159 | if (this.isEmpty()) { 160 | return null; 161 | } 162 | var current = opt_startNode || this.root_; 163 | while (current.right) { 164 | current = current.right; 165 | } 166 | return current; 167 | }; 168 | 169 | 170 | /** 171 | * @return {SplayTree.Node} Node having the maximum key value that 172 | * is less or equal to the specified key value. 173 | */ 174 | SplayTree.prototype.findGreatestLessThan = function(key) { 175 | if (this.isEmpty()) { 176 | return null; 177 | } 178 | // Splay on the key to move the node with the given key or the last 179 | // node on the search path to the top of the tree. 180 | this.splay_(key); 181 | // Now the result is either the root node or the greatest node in 182 | // the left subtree. 183 | if (this.root_.key <= key) { 184 | return this.root_; 185 | } else if (this.root_.left) { 186 | return this.findMax(this.root_.left); 187 | } else { 188 | return null; 189 | } 190 | }; 191 | 192 | 193 | /** 194 | * @return {Array<*>} An array containing all the values of tree's nodes paired 195 | * with keys. 196 | */ 197 | SplayTree.prototype.exportKeysAndValues = function() { 198 | var result = []; 199 | this.traverse_(function(node) { result.push([node.key, node.value]); }); 200 | return result; 201 | }; 202 | 203 | 204 | /** 205 | * @return {Array<*>} An array containing all the values of tree's nodes. 206 | */ 207 | SplayTree.prototype.exportValues = function() { 208 | var result = []; 209 | this.traverse_(function(node) { result.push(node.value); }); 210 | return result; 211 | }; 212 | 213 | 214 | /** 215 | * Perform the splay operation for the given key. Moves the node with 216 | * the given key to the top of the tree. If no node has the given 217 | * key, the last node on the search path is moved to the top of the 218 | * tree. This is the simplified top-down splaying algorithm from: 219 | * "Self-adjusting Binary Search Trees" by Sleator and Tarjan 220 | * 221 | * @param {number} key Key to splay the tree on. 222 | * @private 223 | */ 224 | SplayTree.prototype.splay_ = function(key) { 225 | if (this.isEmpty()) { 226 | return; 227 | } 228 | // Create a dummy node. The use of the dummy node is a bit 229 | // counter-intuitive: The right child of the dummy node will hold 230 | // the L tree of the algorithm. The left child of the dummy node 231 | // will hold the R tree of the algorithm. Using a dummy node, left 232 | // and right will always be nodes and we avoid special cases. 233 | var dummy, left, right; 234 | dummy = left = right = new SplayTree.Node(null, null); 235 | var current = this.root_; 236 | while (true) { 237 | if (key < current.key) { 238 | if (!current.left) { 239 | break; 240 | } 241 | if (key < current.left.key) { 242 | // Rotate right. 243 | var tmp = current.left; 244 | current.left = tmp.right; 245 | tmp.right = current; 246 | current = tmp; 247 | if (!current.left) { 248 | break; 249 | } 250 | } 251 | // Link right. 252 | right.left = current; 253 | right = current; 254 | current = current.left; 255 | } else if (key > current.key) { 256 | if (!current.right) { 257 | break; 258 | } 259 | if (key > current.right.key) { 260 | // Rotate left. 261 | var tmp = current.right; 262 | current.right = tmp.left; 263 | tmp.left = current; 264 | current = tmp; 265 | if (!current.right) { 266 | break; 267 | } 268 | } 269 | // Link left. 270 | left.right = current; 271 | left = current; 272 | current = current.right; 273 | } else { 274 | break; 275 | } 276 | } 277 | // Assemble. 278 | left.right = current.left; 279 | right.left = current.right; 280 | current.left = dummy.right; 281 | current.right = dummy.left; 282 | this.root_ = current; 283 | }; 284 | 285 | 286 | /** 287 | * Performs a preorder traversal of the tree. 288 | * 289 | * @param {function(SplayTree.Node)} f Visitor function. 290 | * @private 291 | */ 292 | SplayTree.prototype.traverse_ = function(f) { 293 | var nodesToVisit = [this.root_]; 294 | while (nodesToVisit.length > 0) { 295 | var node = nodesToVisit.shift(); 296 | if (node == null) { 297 | continue; 298 | } 299 | f(node); 300 | nodesToVisit.push(node.left); 301 | nodesToVisit.push(node.right); 302 | } 303 | }; 304 | 305 | 306 | /** 307 | * Constructs a Splay tree node. 308 | * 309 | * @param {number} key Key. 310 | * @param {*} value Value. 311 | */ 312 | SplayTree.Node = function(key, value) { 313 | this.key = key; 314 | this.value = value; 315 | }; 316 | 317 | 318 | /** 319 | * @type {SplayTree.Node} 320 | */ 321 | SplayTree.Node.prototype.left = null; 322 | 323 | 324 | /** 325 | * @type {SplayTree.Node} 326 | */ 327 | SplayTree.Node.prototype.right = null; 328 | module.exports = SplayTree; 329 | -------------------------------------------------------------------------------- /lib/tickprocessor.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 the V8 project authors. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are 4 | // met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above 9 | // copyright notice, this list of conditions and the following 10 | // disclaimer in the documentation and/or other materials provided 11 | // with the distribution. 12 | // * Neither the name of Google Inc. nor the names of its 13 | // contributors may be used to endorse or promote products derived 14 | // from this software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | var profileModule = require('./profile'); 29 | var Profile = profileModule.Profile; 30 | var logReaderModule = require('./logreader'); 31 | var LogReader = logReaderModule.LogReader; 32 | var ProfileView = require('./profile_view'); 33 | var print = console.log.bind(console); 34 | var fs = require('fs'); 35 | 36 | var os = { 37 | system: function(name, args) { 38 | return require('child_process').execSync(name + ' ' + args.join(' ')).toString(); 39 | } 40 | } 41 | 42 | function processFileLines(fileName, processLine, done) { 43 | var logStream = fs.createReadStream(fileName); 44 | var lineStream = require('byline').createStream(logStream); 45 | lineStream.on('data', processLine); 46 | lineStream.on('end', done); 47 | } 48 | 49 | function inherits(childCtor, parentCtor) { 50 | childCtor.prototype.__proto__ = parentCtor.prototype; 51 | }; 52 | 53 | 54 | function V8Profile(separateIc) { 55 | Profile.call(this); 56 | if (!separateIc) { 57 | this.skipThisFunction = function(name) { return V8Profile.IC_RE.test(name); }; 58 | } 59 | }; 60 | inherits(V8Profile, Profile); 61 | 62 | 63 | V8Profile.IC_RE = 64 | /^(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Call|Load|Store)IC_)/; 65 | 66 | 67 | /** 68 | * A thin wrapper around shell's 'read' function showing a file name on error. 69 | */ 70 | function readFile(fileName) { 71 | try { 72 | return read(fileName); 73 | } catch (e) { 74 | print(fileName + ': ' + (e.message || e)); 75 | throw e; 76 | } 77 | } 78 | 79 | 80 | /** 81 | * Parser for dynamic code optimization state. 82 | */ 83 | function parseState(s) { 84 | switch (s) { 85 | case "": return Profile.CodeState.COMPILED; 86 | case "~": return Profile.CodeState.OPTIMIZABLE; 87 | case "*": return Profile.CodeState.OPTIMIZED; 88 | } 89 | throw new Error("unknown code state: " + s); 90 | } 91 | 92 | 93 | function SnapshotLogProcessor(ignoreUnknown) { 94 | LogReader.call(this, { 95 | 'code-creation': { 96 | parsers: [null, parseInt, parseInt, parseInt, null, 'var-args'], 97 | processor: this.processCodeCreation }, 98 | 'code-move': { parsers: [parseInt, parseInt], 99 | processor: this.processCodeMove }, 100 | 'code-delete': { parsers: [parseInt], 101 | processor: this.processCodeDelete }, 102 | 'function-creation': null, 103 | 'function-move': null, 104 | 'function-delete': null, 105 | 'sfi-move': null, 106 | 'snapshot-pos': { parsers: [parseInt, parseInt], 107 | processor: this.processSnapshotPosition }}); 108 | 109 | V8Profile.prototype.handleUnknownCode = function(operation, addr) { 110 | if (ignoreUnknown) return; 111 | var op = Profile.Operation; 112 | switch (operation) { 113 | case op.MOVE: 114 | print('Snapshot: Code move event for unknown code: 0x' + 115 | addr.toString(16)); 116 | break; 117 | case op.DELETE: 118 | print('Snapshot: Code delete event for unknown code: 0x' + 119 | addr.toString(16)); 120 | break; 121 | } 122 | }; 123 | 124 | this.profile_ = new V8Profile(); 125 | this.serializedEntries_ = []; 126 | } 127 | inherits(SnapshotLogProcessor, LogReader); 128 | 129 | 130 | SnapshotLogProcessor.prototype.processCodeCreation = function( 131 | type, kind, start, size, name, maybe_func) { 132 | if (maybe_func.length) { 133 | var funcAddr = parseInt(maybe_func[0]); 134 | var state = parseState(maybe_func[1]); 135 | this.profile_.addFuncCode(type, name, start, size, funcAddr, state); 136 | } else { 137 | this.profile_.addCode(type, name, start, size); 138 | } 139 | }; 140 | 141 | 142 | SnapshotLogProcessor.prototype.processCodeMove = function(from, to) { 143 | this.profile_.moveCode(from, to); 144 | }; 145 | 146 | 147 | SnapshotLogProcessor.prototype.processCodeDelete = function(start) { 148 | this.profile_.deleteCode(start); 149 | }; 150 | 151 | 152 | SnapshotLogProcessor.prototype.processSnapshotPosition = function(addr, pos) { 153 | this.serializedEntries_[pos] = this.profile_.findEntry(addr); 154 | }; 155 | 156 | 157 | SnapshotLogProcessor.prototype.processLogFile = function(fileName, done) { 158 | processFileLines(fileName, this.processLogChunk.bind(this), done); 159 | }; 160 | 161 | 162 | SnapshotLogProcessor.prototype.getSerializedEntryName = function(pos) { 163 | var entry = this.serializedEntries_[pos]; 164 | return entry ? entry.getRawName() : null; 165 | }; 166 | 167 | 168 | function TickProcessor( 169 | cppEntriesProvider, 170 | separateIc, 171 | callGraphSize, 172 | ignoreUnknown, 173 | stateFilter, 174 | snapshotLogProcessor, 175 | distortion, 176 | range, 177 | sourceMap) { 178 | LogReader.call(this, { 179 | 'shared-library': { parsers: [null, parseInt, parseInt], 180 | processor: this.processSharedLibrary }, 181 | 'code-creation': { 182 | parsers: [null, parseInt, parseInt, parseInt, null, 'var-args'], 183 | processor: this.processCodeCreation }, 184 | 'code-move': { parsers: [parseInt, parseInt], 185 | processor: this.processCodeMove }, 186 | 'code-delete': { parsers: [parseInt], 187 | processor: this.processCodeDelete }, 188 | 'sfi-move': { parsers: [parseInt, parseInt], 189 | processor: this.processFunctionMove }, 190 | 'snapshot-pos': { parsers: [parseInt, parseInt], 191 | processor: this.processSnapshotPosition }, 192 | 'tick': { 193 | parsers: [parseInt, parseInt, parseInt, 194 | parseInt, parseInt, 'var-args'], 195 | processor: this.processTick }, 196 | 'heap-sample-begin': { parsers: [null, null, parseInt], 197 | processor: this.processHeapSampleBegin }, 198 | 'heap-sample-end': { parsers: [null, null], 199 | processor: this.processHeapSampleEnd }, 200 | 'timer-event-start' : { parsers: [null, null, null], 201 | processor: this.advanceDistortion }, 202 | 'timer-event-end' : { parsers: [null, null, null], 203 | processor: this.advanceDistortion }, 204 | // Ignored events. 205 | 'profiler': null, 206 | 'function-creation': null, 207 | 'function-move': null, 208 | 'function-delete': null, 209 | 'heap-sample-item': null, 210 | // Obsolete row types. 211 | 'code-allocate': null, 212 | 'begin-code-region': null, 213 | 'end-code-region': null }); 214 | 215 | this.cppEntriesProvider_ = cppEntriesProvider; 216 | this.callGraphSize_ = callGraphSize; 217 | this.ignoreUnknown_ = ignoreUnknown; 218 | this.stateFilter_ = stateFilter; 219 | this.snapshotLogProcessor_ = snapshotLogProcessor; 220 | this.sourceMap = sourceMap; 221 | this.deserializedEntriesNames_ = []; 222 | var ticks = this.ticks_ = 223 | { total: 0, unaccounted: 0, excluded: 0, gc: 0 }; 224 | 225 | distortion = parseInt(distortion); 226 | // Convert picoseconds to nanoseconds. 227 | this.distortion_per_entry = isNaN(distortion) ? 0 : (distortion / 1000); 228 | this.distortion = 0; 229 | var rangelimits = range ? range.split(",") : []; 230 | var range_start = parseInt(rangelimits[0]); 231 | var range_end = parseInt(rangelimits[1]); 232 | // Convert milliseconds to nanoseconds. 233 | this.range_start = isNaN(range_start) ? -Infinity : (range_start * 1000); 234 | this.range_end = isNaN(range_end) ? Infinity : (range_end * 1000) 235 | 236 | V8Profile.prototype.handleUnknownCode = function( 237 | operation, addr, opt_stackPos) { 238 | if (ignoreUnknown) return; 239 | var op = Profile.Operation; 240 | switch (operation) { 241 | case op.MOVE: 242 | print('Code move event for unknown code: 0x' + addr.toString(16)); 243 | break; 244 | case op.DELETE: 245 | print('Code delete event for unknown code: 0x' + addr.toString(16)); 246 | break; 247 | case op.TICK: 248 | // Only unknown PCs (the first frame) are reported as unaccounted, 249 | // otherwise tick balance will be corrupted (this behavior is compatible 250 | // with the original tickprocessor.py script.) 251 | if (opt_stackPos == 0) { 252 | ticks.unaccounted++; 253 | } 254 | break; 255 | } 256 | }; 257 | 258 | this.profile_ = new V8Profile(separateIc); 259 | this.codeTypes_ = {}; 260 | // Count each tick as a time unit. 261 | this.viewBuilder_ = new ProfileView.ViewBuilder(1); 262 | this.lastLogFileName_ = null; 263 | 264 | this.generation_ = 1; 265 | this.currentProducerProfile_ = null; 266 | }; 267 | inherits(TickProcessor, LogReader); 268 | 269 | 270 | TickProcessor.VmStates = { 271 | JS: 0, 272 | GC: 1, 273 | COMPILER: 2, 274 | OTHER: 3, 275 | EXTERNAL: 4, 276 | IDLE: 5 277 | }; 278 | 279 | 280 | TickProcessor.CodeTypes = { 281 | CPP: 0, 282 | SHARED_LIB: 1 283 | }; 284 | // Otherwise, this is JS-related code. We are not adding it to 285 | // codeTypes_ map because there can be zillions of them. 286 | 287 | 288 | TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0; 289 | 290 | TickProcessor.CALL_GRAPH_SIZE = 5; 291 | 292 | TickProcessor.TOP_DOWN_PROFILE_CUTOFF_PCT = 0.1; 293 | 294 | /** 295 | * @override 296 | */ 297 | TickProcessor.prototype.printError = function(str) { 298 | print(str); 299 | }; 300 | 301 | 302 | TickProcessor.prototype.setCodeType = function(name, type) { 303 | this.codeTypes_[name] = TickProcessor.CodeTypes[type]; 304 | }; 305 | 306 | 307 | TickProcessor.prototype.isSharedLibrary = function(name) { 308 | return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB; 309 | }; 310 | 311 | 312 | TickProcessor.prototype.isCppCode = function(name) { 313 | return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP; 314 | }; 315 | 316 | 317 | TickProcessor.prototype.isJsCode = function(name) { 318 | return !(name in this.codeTypes_); 319 | }; 320 | 321 | 322 | TickProcessor.prototype.processLogFile = function(fileName, done) { 323 | this.lastLogFileName_ = fileName; 324 | processFileLines(fileName, this.processLogLine.bind(this), done); 325 | }; 326 | 327 | 328 | TickProcessor.prototype.processLogFileInTest = function(fileName) { 329 | // Hack file name to avoid dealing with platform specifics. 330 | this.lastLogFileName_ = 'v8.log'; 331 | var contents = readFile(fileName); 332 | this.processLogChunk(contents); 333 | }; 334 | 335 | 336 | TickProcessor.prototype.processSharedLibrary = function( 337 | name, startAddr, endAddr) { 338 | var entry = this.profile_.addLibrary(name, startAddr, endAddr); 339 | this.setCodeType(entry.getName(), 'SHARED_LIB'); 340 | 341 | var self = this; 342 | var libFuncs = this.cppEntriesProvider_.parseVmSymbols( 343 | name, startAddr, endAddr, function(fName, fStart, fEnd) { 344 | self.profile_.addStaticCode(fName, fStart, fEnd); 345 | self.setCodeType(fName, 'CPP'); 346 | }); 347 | }; 348 | 349 | 350 | TickProcessor.prototype.processCodeCreation = function( 351 | type, kind, start, size, name, maybe_func) { 352 | name = this.deserializedEntriesNames_[start] || name; 353 | if (maybe_func.length) { 354 | var funcAddr = parseInt(maybe_func[0]); 355 | var state = parseState(maybe_func[1]); 356 | this.profile_.addFuncCode(type, name, start, size, funcAddr, state); 357 | } else { 358 | this.profile_.addCode(type, name, start, size); 359 | } 360 | }; 361 | 362 | 363 | TickProcessor.prototype.processCodeMove = function(from, to) { 364 | this.profile_.moveCode(from, to); 365 | }; 366 | 367 | 368 | TickProcessor.prototype.processCodeDelete = function(start) { 369 | this.profile_.deleteCode(start); 370 | }; 371 | 372 | 373 | TickProcessor.prototype.processFunctionMove = function(from, to) { 374 | this.profile_.moveFunc(from, to); 375 | }; 376 | 377 | 378 | TickProcessor.prototype.processSnapshotPosition = function(addr, pos) { 379 | if (this.snapshotLogProcessor_) { 380 | this.deserializedEntriesNames_[addr] = 381 | this.snapshotLogProcessor_.getSerializedEntryName(pos); 382 | } 383 | }; 384 | 385 | 386 | TickProcessor.prototype.includeTick = function(vmState) { 387 | return this.stateFilter_ == null || this.stateFilter_ == vmState; 388 | }; 389 | 390 | TickProcessor.prototype.processTick = function(pc, 391 | ns_since_start, 392 | is_external_callback, 393 | tos_or_external_callback, 394 | vmState, 395 | stack) { 396 | this.distortion += this.distortion_per_entry; 397 | ns_since_start -= this.distortion; 398 | if (ns_since_start < this.range_start || ns_since_start > this.range_end) { 399 | return; 400 | } 401 | this.ticks_.total++; 402 | if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++; 403 | if (!this.includeTick(vmState)) { 404 | this.ticks_.excluded++; 405 | return; 406 | } 407 | if (is_external_callback) { 408 | // Don't use PC when in external callback code, as it can point 409 | // inside callback's code, and we will erroneously report 410 | // that a callback calls itself. Instead we use tos_or_external_callback, 411 | // as simply resetting PC will produce unaccounted ticks. 412 | pc = tos_or_external_callback; 413 | tos_or_external_callback = 0; 414 | } else if (tos_or_external_callback) { 415 | // Find out, if top of stack was pointing inside a JS function 416 | // meaning that we have encountered a frameless invocation. 417 | var funcEntry = this.profile_.findEntry(tos_or_external_callback); 418 | if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) { 419 | tos_or_external_callback = 0; 420 | } 421 | } 422 | 423 | this.profile_.recordTick(this.processStack(pc, tos_or_external_callback, stack)); 424 | }; 425 | 426 | 427 | TickProcessor.prototype.advanceDistortion = function() { 428 | this.distortion += this.distortion_per_entry; 429 | } 430 | 431 | 432 | TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) { 433 | if (space != 'Heap') return; 434 | this.currentProducerProfile_ = new CallTree(); 435 | }; 436 | 437 | 438 | TickProcessor.prototype.processHeapSampleEnd = function(space, state) { 439 | if (space != 'Heap' || !this.currentProducerProfile_) return; 440 | 441 | print('Generation ' + this.generation_ + ':'); 442 | var tree = this.currentProducerProfile_; 443 | tree.computeTotalWeights(); 444 | var producersView = this.viewBuilder_.buildView(tree); 445 | // Sort by total time, desc, then by name, desc. 446 | producersView.sort(function(rec1, rec2) { 447 | return rec2.totalTime - rec1.totalTime || 448 | (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 449 | this.printHeavyProfile(producersView.head.children); 450 | 451 | this.currentProducerProfile_ = null; 452 | this.generation_++; 453 | }; 454 | 455 | 456 | TickProcessor.prototype.printStatistics = function() { 457 | print('Statistical profiling result from ' + this.lastLogFileName_ + 458 | ', (' + this.ticks_.total + 459 | ' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' + 460 | this.ticks_.excluded + ' excluded).'); 461 | 462 | if (this.ticks_.total == 0) return; 463 | 464 | // Print the unknown ticks percentage if they are not ignored. 465 | if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) { 466 | this.printHeader('Unknown'); 467 | this.printCounter(this.ticks_.unaccounted, this.ticks_.total); 468 | } 469 | 470 | var flatProfile = this.profile_.getFlatProfile(); 471 | var flatView = this.viewBuilder_.buildView(flatProfile); 472 | // Sort by self time, desc, then by name, desc. 473 | flatView.sort(function(rec1, rec2) { 474 | return rec2.selfTime - rec1.selfTime || 475 | (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 476 | var totalTicks = this.ticks_.total; 477 | if (this.ignoreUnknown_) { 478 | totalTicks -= this.ticks_.unaccounted; 479 | } 480 | // Our total time contains all the ticks encountered, 481 | // while profile only knows about the filtered ticks. 482 | flatView.head.totalTime = totalTicks; 483 | 484 | // Count library ticks 485 | var flatViewNodes = flatView.head.children; 486 | var self = this; 487 | var libraryTicks = 0; 488 | this.processProfile(flatViewNodes, 489 | function(name) { return self.isSharedLibrary(name); }, 490 | function(rec) { libraryTicks += rec.selfTime; }); 491 | var nonLibraryTicks = totalTicks - libraryTicks; 492 | 493 | this.printHeader('Shared libraries'); 494 | this.printEntries(flatViewNodes, null, 495 | function(name) { return self.isSharedLibrary(name); }); 496 | 497 | this.printHeader('JavaScript'); 498 | this.printEntries(flatViewNodes, nonLibraryTicks, 499 | function(name) { return self.isJsCode(name); }); 500 | 501 | this.printHeader('C++'); 502 | this.printEntries(flatViewNodes, nonLibraryTicks, 503 | function(name) { return self.isCppCode(name); }); 504 | 505 | this.printHeader('GC'); 506 | this.printCounter(this.ticks_.gc, totalTicks); 507 | 508 | this.printHeavyProfHeader(); 509 | var heavyProfile = this.profile_.getBottomUpProfile(); 510 | var heavyView = this.viewBuilder_.buildView(heavyProfile); 511 | // To show the same percentages as in the flat profile. 512 | heavyView.head.totalTime = totalTicks; 513 | // Sort by total time, desc, then by name, desc. 514 | heavyView.sort(function(rec1, rec2) { 515 | return rec2.totalTime - rec1.totalTime || 516 | (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 517 | this.printHeavyProfile(heavyView.head.children); 518 | 519 | this.printTopDownProfHeader(); 520 | var topDownProfile = this.profile_.getTopDownProfile(); 521 | var topDownView = this.viewBuilder_.buildView(topDownProfile); 522 | // To show the same percentages as in the flat profile. 523 | topDownView.head.totalTime = totalTicks; 524 | // Sort by total time, desc, then by name, desc. 525 | topDownView.sort(function(rec1, rec2) { 526 | return rec2.totalTime - rec1.totalTime || 527 | (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 528 | this.printTopDownProfile(topDownView.head.children); 529 | }; 530 | 531 | 532 | function padLeft(s, len) { 533 | s = s.toString(); 534 | if (s.length < len) { 535 | var padLength = len - s.length; 536 | if (!(padLength in padLeft)) { 537 | padLeft[padLength] = new Array(padLength + 1).join(' '); 538 | } 539 | s = padLeft[padLength] + s; 540 | } 541 | return s; 542 | }; 543 | 544 | 545 | TickProcessor.prototype.printHeader = function(headerTitle) { 546 | print('\n [' + headerTitle + ']:'); 547 | print(' ticks total nonlib name'); 548 | }; 549 | 550 | 551 | TickProcessor.prototype.printHeavyProfHeader = function() { 552 | print('\n [Bottom up (heavy) profile]:'); 553 | print(' Note: percentage shows a share of a particular caller in the ' + 554 | 'total\n' + 555 | ' amount of its parent calls.'); 556 | print(' Callers occupying less than ' + 557 | TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) + 558 | '% are not shown.\n'); 559 | print(' ticks parent name'); 560 | }; 561 | 562 | 563 | TickProcessor.prototype.printTopDownProfHeader = function() { 564 | print('\n [Top down (heavy) profile]:'); 565 | print(' Note: callees occupying less than ' + 566 | TickProcessor.TOP_DOWN_PROFILE_CUTOFF_PCT.toFixed(1) + 567 | '% are not shown.\n'); 568 | print(' inclusive self name') 569 | print(' ticks total ticks total'); 570 | }; 571 | 572 | 573 | TickProcessor.prototype.printCounter = function(ticksCount, totalTicksCount) { 574 | var pct = ticksCount * 100.0 / totalTicksCount; 575 | print(' ' + padLeft(ticksCount, 5) + ' ' + padLeft(pct.toFixed(1), 5) + '%'); 576 | }; 577 | 578 | 579 | TickProcessor.prototype.processProfile = function( 580 | profile, filterP, func) { 581 | for (var i = 0, n = profile.length; i < n; ++i) { 582 | var rec = profile[i]; 583 | if (!filterP(rec.internalFuncName)) { 584 | continue; 585 | } 586 | func(rec); 587 | } 588 | }; 589 | 590 | TickProcessor.prototype.getLineAndColumn = function(name) { 591 | var re = /:([0-9]+):([0-9]+)$/; 592 | var array = re.exec(name); 593 | if (!array) { 594 | return null; 595 | } 596 | return {line: array[1], column: array[2]}; 597 | } 598 | 599 | TickProcessor.prototype.hasSourceMap = function() { 600 | return this.sourceMap != null; 601 | }; 602 | 603 | 604 | TickProcessor.prototype.formatFunctionName = function(funcName) { 605 | if (!this.hasSourceMap()) { 606 | return funcName; 607 | } 608 | var lc = this.getLineAndColumn(funcName); 609 | if (lc == null) { 610 | return funcName; 611 | } 612 | // in source maps lines and columns are zero based 613 | var lineNumber = lc.line - 1; 614 | var column = lc.column - 1; 615 | var entry = this.sourceMap.findEntry(lineNumber, column); 616 | var sourceFile = entry[2]; 617 | var sourceLine = entry[3] + 1; 618 | var sourceColumn = entry[4] + 1; 619 | 620 | return sourceFile + ':' + sourceLine + ':' + sourceColumn + ' -> ' + funcName; 621 | }; 622 | 623 | TickProcessor.prototype.printEntries = function( 624 | profile, nonLibTicks, filterP) { 625 | var that = this; 626 | this.processProfile(profile, filterP, function (rec) { 627 | if (rec.selfTime == 0) return; 628 | var nonLibPct = nonLibTicks != null ? 629 | rec.selfTime * 100.0 / nonLibTicks : 0.0; 630 | var funcName = that.formatFunctionName(rec.internalFuncName); 631 | 632 | print(' ' + padLeft(rec.selfTime, 5) + ' ' + 633 | padLeft(rec.selfPercent.toFixed(1), 5) + '% ' + 634 | padLeft(nonLibPct.toFixed(1), 5) + '% ' + 635 | funcName); 636 | }); 637 | }; 638 | 639 | 640 | TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) { 641 | var self = this; 642 | var indent = opt_indent || 0; 643 | var indentStr = padLeft('', indent); 644 | this.processProfile(profile, function() { return true; }, function (rec) { 645 | // Cut off too infrequent callers. 646 | if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return; 647 | var funcName = self.formatFunctionName(rec.internalFuncName); 648 | print(' ' + padLeft(rec.totalTime, 5) + ' ' + 649 | padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' + 650 | indentStr + funcName); 651 | // Limit backtrace depth. 652 | if (indent < 2 * self.callGraphSize_) { 653 | self.printHeavyProfile(rec.children, indent + 2); 654 | } 655 | // Delimit top-level functions. 656 | if (indent == 0) { 657 | print(''); 658 | } 659 | }); 660 | }; 661 | 662 | 663 | TickProcessor.prototype.printTopDownProfile = function(profile, opt_indent) { 664 | var self = this; 665 | var indent = opt_indent || 0; 666 | var indentStr = padLeft('', indent); 667 | this.processProfile(profile, function() { return true; }, function (rec) { 668 | // Cut off too infrequent callers. 669 | if (rec.totalPercent < TickProcessor.TOP_DOWN_PROFILE_CUTOFF_PCT) return; 670 | print(' ' + padLeft(rec.totalTime, 5) + ' ' + 671 | padLeft(rec.totalPercent.toFixed(1), 5) + '% ' + 672 | padLeft(rec.selfTime, 5) + ' ' + 673 | padLeft(rec.selfPercent.toFixed(1), 5) + '% ' + 674 | indentStr + rec.internalFuncName); 675 | self.printTopDownProfile(rec.children, indent + 2); 676 | // Delimit top-level functions. 677 | if (indent == 0) { 678 | print(''); 679 | } 680 | }); 681 | }; 682 | 683 | 684 | function CppEntriesProvider() { 685 | }; 686 | 687 | 688 | CppEntriesProvider.prototype.parseVmSymbols = function( 689 | libName, libStart, libEnd, processorFunc) { 690 | this.loadSymbols(libName); 691 | 692 | var prevEntry; 693 | 694 | function addEntry(funcInfo) { 695 | // Several functions can be mapped onto the same address. To avoid 696 | // creating zero-sized entries, skip such duplicates. 697 | // Also double-check that function belongs to the library address space. 698 | if (prevEntry && !prevEntry.end && 699 | prevEntry.start < funcInfo.start && 700 | prevEntry.start >= libStart && funcInfo.start <= libEnd) { 701 | processorFunc(prevEntry.name, prevEntry.start, funcInfo.start); 702 | } 703 | if (funcInfo.end && 704 | (!prevEntry || prevEntry.start != funcInfo.start) && 705 | funcInfo.start >= libStart && funcInfo.end <= libEnd) { 706 | processorFunc(funcInfo.name, funcInfo.start, funcInfo.end); 707 | } 708 | prevEntry = funcInfo; 709 | } 710 | 711 | while (true) { 712 | var funcInfo = this.parseNextLine(); 713 | if (funcInfo === null) { 714 | continue; 715 | } else if (funcInfo === false) { 716 | break; 717 | } 718 | if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) { 719 | funcInfo.start += libStart; 720 | } 721 | if (funcInfo.size) { 722 | funcInfo.end = funcInfo.start + funcInfo.size; 723 | } 724 | addEntry(funcInfo); 725 | } 726 | addEntry({name: '', start: libEnd}); 727 | }; 728 | 729 | 730 | CppEntriesProvider.prototype.loadSymbols = function(libName) { 731 | }; 732 | 733 | 734 | CppEntriesProvider.prototype.parseNextLine = function() { 735 | return false; 736 | }; 737 | 738 | 739 | function UnixCppEntriesProvider(nmExec, targetRootFS) { 740 | this.symbols = []; 741 | this.parsePos = 0; 742 | this.nmExec = nmExec; 743 | this.targetRootFS = targetRootFS; 744 | this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/; 745 | }; 746 | inherits(UnixCppEntriesProvider, CppEntriesProvider); 747 | 748 | UnixCppEntriesProvider.prototype.loadSymbols = function(libName) { 749 | this.parsePos = 0; 750 | libName = this.targetRootFS + libName; 751 | try { 752 | this.symbols = [ 753 | os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1), 754 | os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1) 755 | ]; 756 | } catch (e) { 757 | // If the library cannot be found on this system let's not panic. 758 | this.symbols = ['', '']; 759 | } 760 | }; 761 | 762 | 763 | UnixCppEntriesProvider.prototype.parseNextLine = function() { 764 | if (this.symbols.length == 0) { 765 | return false; 766 | } 767 | var lineEndPos = this.symbols[0].indexOf('\n', this.parsePos); 768 | if (lineEndPos == -1) { 769 | this.symbols.shift(); 770 | this.parsePos = 0; 771 | return this.parseNextLine(); 772 | } 773 | 774 | var line = this.symbols[0].substring(this.parsePos, lineEndPos); 775 | this.parsePos = lineEndPos + 1; 776 | var fields = line.match(this.FUNC_RE); 777 | var funcInfo = null; 778 | if (fields) { 779 | funcInfo = { name: fields[3], start: parseInt(fields[1], 16) }; 780 | if (fields[2]) { 781 | funcInfo.size = parseInt(fields[2], 16); 782 | } 783 | } 784 | return funcInfo; 785 | }; 786 | 787 | 788 | function MacCppEntriesProvider(nmExec, targetRootFS) { 789 | UnixCppEntriesProvider.call(this, nmExec, targetRootFS); 790 | // Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups. 791 | this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ()[iItT] (.*)$/; 792 | }; 793 | inherits(MacCppEntriesProvider, UnixCppEntriesProvider); 794 | 795 | 796 | MacCppEntriesProvider.prototype.loadSymbols = function(libName) { 797 | this.parsePos = 0; 798 | libName = this.targetRootFS + libName; 799 | try { 800 | this.symbols = [os.system(this.nmExec, ['-n', '-f', libName], -1, -1), '']; 801 | } catch (e) { 802 | // If the library cannot be found on this system let's not panic. 803 | this.symbols = ''; 804 | } 805 | }; 806 | 807 | 808 | function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS) { 809 | this.targetRootFS = targetRootFS; 810 | this.symbols = ''; 811 | this.parsePos = 0; 812 | }; 813 | inherits(WindowsCppEntriesProvider, CppEntriesProvider); 814 | 815 | 816 | WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/; 817 | 818 | 819 | WindowsCppEntriesProvider.FUNC_RE = 820 | /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/; 821 | 822 | 823 | WindowsCppEntriesProvider.IMAGE_BASE_RE = 824 | /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/; 825 | 826 | 827 | // This is almost a constant on Windows. 828 | WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000; 829 | 830 | 831 | WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) { 832 | libName = this.targetRootFS + libName; 833 | var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE); 834 | if (!fileNameFields) return; 835 | var mapFileName = fileNameFields[1] + '.map'; 836 | this.moduleType_ = fileNameFields[2].toLowerCase(); 837 | try { 838 | this.symbols = read(mapFileName); 839 | } catch (e) { 840 | // If .map file cannot be found let's not panic. 841 | this.symbols = ''; 842 | } 843 | }; 844 | 845 | 846 | WindowsCppEntriesProvider.prototype.parseNextLine = function() { 847 | var lineEndPos = this.symbols.indexOf('\r\n', this.parsePos); 848 | if (lineEndPos == -1) { 849 | return false; 850 | } 851 | 852 | var line = this.symbols.substring(this.parsePos, lineEndPos); 853 | this.parsePos = lineEndPos + 2; 854 | 855 | // Image base entry is above all other symbols, so we can just 856 | // terminate parsing. 857 | var imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE); 858 | if (imageBaseFields) { 859 | var imageBase = parseInt(imageBaseFields[1], 16); 860 | if ((this.moduleType_ == 'exe') != 861 | (imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) { 862 | return false; 863 | } 864 | } 865 | 866 | var fields = line.match(WindowsCppEntriesProvider.FUNC_RE); 867 | return fields ? 868 | { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } : 869 | null; 870 | }; 871 | 872 | 873 | /** 874 | * Performs very simple unmangling of C++ names. 875 | * 876 | * Does not handle arguments and template arguments. The mangled names have 877 | * the form: 878 | * 879 | * ?LookupInDescriptor@JSObject@internal@v8@@...arguments info... 880 | */ 881 | WindowsCppEntriesProvider.prototype.unmangleName = function(name) { 882 | // Empty or non-mangled name. 883 | if (name.length < 1 || name.charAt(0) != '?') return name; 884 | var nameEndPos = name.indexOf('@@'); 885 | var components = name.substring(1, nameEndPos).split('@'); 886 | components.reverse(); 887 | return components.join('::'); 888 | }; 889 | 890 | 891 | function ArgumentsProcessor(args) { 892 | this.args_ = args; 893 | this.result_ = ArgumentsProcessor.DEFAULTS; 894 | 895 | this.argsDispatch_ = { 896 | '-j': ['stateFilter', TickProcessor.VmStates.JS, 897 | 'Show only ticks from JS VM state'], 898 | '-g': ['stateFilter', TickProcessor.VmStates.GC, 899 | 'Show only ticks from GC VM state'], 900 | '-c': ['stateFilter', TickProcessor.VmStates.COMPILER, 901 | 'Show only ticks from COMPILER VM state'], 902 | '-o': ['stateFilter', TickProcessor.VmStates.OTHER, 903 | 'Show only ticks from OTHER VM state'], 904 | '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL, 905 | 'Show only ticks from EXTERNAL VM state'], 906 | '--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE, 907 | 'Set the call graph size'], 908 | '--ignore-unknown': ['ignoreUnknown', true, 909 | 'Exclude ticks of unknown code entries from processing'], 910 | '--separate-ic': ['separateIc', true, 911 | 'Separate IC entries'], 912 | '--unix': ['platform', 'unix', 913 | 'Specify that we are running on *nix platform'], 914 | '--windows': ['platform', 'windows', 915 | 'Specify that we are running on Windows platform'], 916 | '--mac': ['platform', 'mac', 917 | 'Specify that we are running on Mac OS X platform'], 918 | '--nm': ['nm', 'nm', 919 | 'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'], 920 | '--target': ['targetRootFS', '', 921 | 'Specify the target root directory for cross environment'], 922 | '--snapshot-log': ['snapshotLogFileName', 'snapshot.log', 923 | 'Specify snapshot log file to use (e.g. --snapshot-log=snapshot.log)'], 924 | '--range': ['range', 'auto,auto', 925 | 'Specify the range limit as [start],[end]'], 926 | '--distortion': ['distortion', 0, 927 | 'Specify the logging overhead in picoseconds'], 928 | '--source-map': ['sourceMap', null, 929 | 'Specify the source map that should be used for output'] 930 | }; 931 | this.argsDispatch_['--js'] = this.argsDispatch_['-j']; 932 | this.argsDispatch_['--gc'] = this.argsDispatch_['-g']; 933 | this.argsDispatch_['--compiler'] = this.argsDispatch_['-c']; 934 | this.argsDispatch_['--other'] = this.argsDispatch_['-o']; 935 | this.argsDispatch_['--external'] = this.argsDispatch_['-e']; 936 | }; 937 | 938 | 939 | ArgumentsProcessor.DEFAULTS = { 940 | logFileName: 'v8.log', 941 | snapshotLogFileName: null, 942 | platform: 'unix', 943 | stateFilter: null, 944 | callGraphSize: 5, 945 | ignoreUnknown: false, 946 | separateIc: false, 947 | targetRootFS: '', 948 | nm: 'nm', 949 | range: 'auto,auto', 950 | distortion: 0 951 | }; 952 | 953 | 954 | ArgumentsProcessor.prototype.parse = function() { 955 | while (this.args_.length) { 956 | var arg = this.args_[0]; 957 | if (arg.charAt(0) != '-') { 958 | break; 959 | } 960 | this.args_.shift(); 961 | var userValue = null; 962 | var eqPos = arg.indexOf('='); 963 | if (eqPos != -1) { 964 | userValue = arg.substr(eqPos + 1); 965 | arg = arg.substr(0, eqPos); 966 | } 967 | if (arg in this.argsDispatch_) { 968 | var dispatch = this.argsDispatch_[arg]; 969 | this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue; 970 | } else { 971 | return false; 972 | } 973 | } 974 | 975 | if (this.args_.length >= 1) { 976 | this.result_.logFileName = this.args_.shift(); 977 | } 978 | return true; 979 | }; 980 | 981 | 982 | ArgumentsProcessor.prototype.result = function() { 983 | return this.result_; 984 | }; 985 | 986 | 987 | ArgumentsProcessor.prototype.printUsageAndExit = function() { 988 | 989 | function padRight(s, len) { 990 | s = s.toString(); 991 | if (s.length < len) { 992 | s = s + (new Array(len - s.length + 1).join(' ')); 993 | } 994 | return s; 995 | } 996 | 997 | print('Cmdline args: [options] [log-file-name]\n' + 998 | 'Default log file name is "' + 999 | ArgumentsProcessor.DEFAULTS.logFileName + '".\n'); 1000 | print('Options:'); 1001 | for (var arg in this.argsDispatch_) { 1002 | var synonims = [arg]; 1003 | var dispatch = this.argsDispatch_[arg]; 1004 | for (var synArg in this.argsDispatch_) { 1005 | if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) { 1006 | synonims.push(synArg); 1007 | delete this.argsDispatch_[synArg]; 1008 | } 1009 | } 1010 | print(' ' + padRight(synonims.join(', '), 20) + dispatch[2]); 1011 | } 1012 | process.exit(2); 1013 | }; 1014 | 1015 | module.exports.UnixCppEntriesProvider = UnixCppEntriesProvider; 1016 | module.exports.WindowsCppEntriesProvider = WindowsCppEntriesProvider; 1017 | module.exports.MacCppEntriesProvider = MacCppEntriesProvider; 1018 | module.exports.ArgumentsProcessor = ArgumentsProcessor; 1019 | module.exports.TickProcessor = TickProcessor; 1020 | module.exports.SnapshotLogProcessor = SnapshotLogProcessor; 1021 | module.exports.processFileLines = processFileLines; 1022 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tick", 3 | "version": "0.1.1", 4 | "description": "node-compatible v8.log processor", 5 | "homepage": "http://github.com/sidorares/node-tick", 6 | "author": "Andrey Sidorov ", 7 | "license": "MIT", 8 | "keywords": [ 9 | "v8.log", 10 | "profiler", 11 | "profile", 12 | "tick", 13 | "linux-tick-processor" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "http://github.com/sidorares/node-tick" 18 | }, 19 | "engines": { 20 | "node": ">=0.4.0" 21 | }, 22 | "bin": { 23 | "node-tick-processor": "./bin/tickprocessor-driver.js", 24 | "plot-timer-events": "./bin/stdio.js" 25 | }, 26 | "dependencies": { 27 | "byline": "~2.0.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/generate.js: -------------------------------------------------------------------------------- 1 | function slow() { 2 | var res=0; 3 | for (var i=0; i < 10000000; ++i) 4 | res += i; 5 | return res; 6 | } 7 | 8 | console.log(slow() + slow() + slow()); 9 | --------------------------------------------------------------------------------