├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── binding.gyp ├── package.json ├── profiler.cc └── tools ├── build-nprof ├── nprof-stub.js └── v8 ├── LICENSE ├── codemap.js ├── consarray.js ├── csvparser.js ├── logreader.js ├── profile.js ├── profile_view.js ├── splaytree.js ├── tickprocessor-driver.js └── tickprocessor.js /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /nprof 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Ben Noordhuis 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-profiler 2 | 3 | Access the V8 profiler from node.js - http://nodejs.org/ 4 | 5 | ## Deprecation notice 6 | 7 | This module is deprecated. Its functionality has been subsumed by the 8 | built-in [profiler](https://nodejs.org/docs/latest/api/inspector.html). 9 | 10 | ## A word of advice 11 | 12 | This module is for people who need fine-grained run-time control over the V8 13 | profiler. You don't need it if all you want is wholesale profiling, simply 14 | start `node` with profiling enabled: 15 | 16 | node --prof application.js 17 | 18 | Read on, however, if you want to wield the arcane powers this module grants. 19 | 20 | ## Compiling 21 | 22 | Easy as pie. To build from source: 23 | 24 | node-gyp configure build install 25 | 26 | Or, if you have `npm` installed: 27 | 28 | npm install profiler 29 | 30 | ## Usage 31 | 32 | In most cases you will want to start your application with the profiler in 33 | suspended animation. 34 | 35 | node --prof --prof_lazy --log application.js 36 | 37 | And inside your application: 38 | 39 | var profiler = require('profiler'); 40 | // 41 | // 42 | // 43 | profiler.resume(); 44 | // 45 | // 46 | // 47 | profiler.pause(); 48 | 49 | This results in a v8.log being written to the current directory that you can 50 | process with the bundled `nprof` tool. Run `sh tools/build-nprof` to build it. 51 | 52 | ## Advanced usage 53 | 54 | You will sometimes want to run the garbage collector before profiling 55 | a performance critical section of code. Here is how you do it: 56 | 57 | $ cat application.js 58 | if (typeof gc === 'function') gc(); 59 | profiler.resume(); 60 | // ... 61 | $ node --prof --prof_lazy --log --expose-gc application.js 62 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'profiler', 5 | 'sources': ['profiler.cc'] 6 | }, 7 | 8 | { 9 | 'target_name': 'nprof', 10 | 'conditions': [ 11 | # requires a posix shell 12 | ['OS != "win"', { 13 | 'actions': [{ 14 | 'action_name': 'nprof', 15 | 'inputs': [ 16 | 'tools/nprof-stub.js', 17 | 'tools/v8/splaytree.js', 18 | 'tools/v8/codemap.js', 19 | 'tools/v8/csvparser.js', 20 | 'tools/v8/consarray.js', 21 | 'tools/v8/profile.js', 22 | 'tools/v8/profile_view.js', 23 | 'tools/v8/logreader.js', 24 | 'tools/v8/tickprocessor.js', 25 | 'tools/v8/tickprocessor-driver.js', 26 | ], 27 | 'outputs': ['<(PRODUCT_DIR)/nprof'], 28 | 'action': ['tools/build-nprof', '<@(_outputs)'], 29 | }], 30 | }], 31 | ], 32 | }, 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "profiler", 3 | "version": "1.2.3", 4 | "description": "Access the V8 profiler from node.js", 5 | "keywords": ["profiler", "profiling"], 6 | "homepage": "https://github.com/bnoordhuis/node-profiler", 7 | "main": "build/Release/profiler.node", 8 | "author": { 9 | "name": "Ben Noordhuis", 10 | "email": "info@bnoordhuis.nl", 11 | "url": "http://bnoordhuis.nl/" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/bnoordhuis/node-profiler.git" 16 | }, 17 | "bin": { 18 | "nprof": "nprof" 19 | }, 20 | "scripts": { 21 | "prepublish": "sh tools/build-nprof" 22 | }, 23 | "engines": { 24 | "node": ">=0.8.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /profiler.cc: -------------------------------------------------------------------------------- 1 | #include "v8.h" 2 | #include "node.h" 3 | 4 | using namespace v8; 5 | using namespace node; 6 | 7 | namespace { 8 | 9 | Handle GC(const Arguments& args) { 10 | while (!V8::IdleNotification()); 11 | return Undefined(); 12 | } 13 | 14 | Handle Resume(const Arguments& args) { 15 | V8::ResumeProfiler(); 16 | return Undefined(); 17 | } 18 | 19 | Handle Pause(const Arguments& args) { 20 | V8::PauseProfiler(); 21 | return Undefined(); 22 | } 23 | 24 | void Init(Handle target) { 25 | HandleScope scope; 26 | 27 | target->Set(String::New("gc"), 28 | FunctionTemplate::New(GC)->GetFunction()); 29 | 30 | target->Set(String::New("pause"), 31 | FunctionTemplate::New(Pause)->GetFunction()); 32 | 33 | target->Set(String::New("resume"), 34 | FunctionTemplate::New(Resume)->GetFunction()); 35 | 36 | // mimic browser API 37 | Local global = Context::GetCurrent()->Global(); 38 | Local console_v = global->Get(String::New("console")); 39 | if (console_v.IsEmpty() || !console_v->IsObject()) return; 40 | Local console = console_v->ToObject(); 41 | 42 | console->Set(String::New("profile"), 43 | FunctionTemplate::New(Resume)->GetFunction()); 44 | 45 | console->Set(String::New("profileEnd"), 46 | FunctionTemplate::New(Pause)->GetFunction()); 47 | } 48 | 49 | NODE_MODULE(profiler, Init) 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tools/build-nprof: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | case "$1" in 4 | '') nprof=../nprof ;; 5 | /*) nprof="$1" ;; 6 | *) nprof="$PWD/$1" ;; 7 | esac 8 | 9 | cd "`dirname "$0"`" 10 | 11 | cat \ 12 | nprof-stub.js \ 13 | v8/splaytree.js \ 14 | v8/codemap.js \ 15 | v8/csvparser.js \ 16 | v8/consarray.js \ 17 | v8/profile.js \ 18 | v8/profile_view.js \ 19 | v8/logreader.js \ 20 | v8/tickprocessor.js \ 21 | v8/tickprocessor-driver.js > "$nprof" || exit 1 22 | chmod 755 "$nprof" || exit 2 23 | -------------------------------------------------------------------------------- /tools/nprof-stub.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function quit(status) { 4 | process.exit(status || 0); 5 | } 6 | 7 | function print(s) { 8 | console.log('%s', s); 9 | } 10 | 11 | function read(fileName) { 12 | return require('fs').readFileSync(fileName, 'utf8'); 13 | } 14 | 15 | var readline = (function() { 16 | // find first non-option, use as log file to read, default to v8.log 17 | var args = process.argv.slice(2).concat(['v8.log']).filter(function(arg) { 18 | return !~arg.indexOf('--'); 19 | }); 20 | var i = 0, lines = read(args[0]).split("\n"); 21 | return function() { 22 | return lines[i++]; 23 | }; 24 | })(); 25 | 26 | var arguments = process.argv.slice(2); 27 | -------------------------------------------------------------------------------- /tools/v8/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2006-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 | -------------------------------------------------------------------------------- /tools/v8/codemap.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 mapper that maps addresses into code entries. 31 | * 32 | * @constructor 33 | */ 34 | function CodeMap() { 35 | /** 36 | * Dynamic code entries. Used for JIT compiled code. 37 | */ 38 | this.dynamics_ = new SplayTree(); 39 | 40 | /** 41 | * Name generator for entries having duplicate names. 42 | */ 43 | this.dynamicsNameGen_ = new CodeMap.NameGenerator(); 44 | 45 | /** 46 | * Static code entries. Used for statically compiled code. 47 | */ 48 | this.statics_ = new SplayTree(); 49 | 50 | /** 51 | * Libraries entries. Used for the whole static code libraries. 52 | */ 53 | this.libraries_ = new SplayTree(); 54 | 55 | /** 56 | * Map of memory pages occupied with static code. 57 | */ 58 | this.pages_ = []; 59 | }; 60 | 61 | 62 | /** 63 | * The number of alignment bits in a page address. 64 | */ 65 | CodeMap.PAGE_ALIGNMENT = 12; 66 | 67 | 68 | /** 69 | * Page size in bytes. 70 | */ 71 | CodeMap.PAGE_SIZE = 72 | 1 << CodeMap.PAGE_ALIGNMENT; 73 | 74 | 75 | /** 76 | * Adds a dynamic (i.e. moveable and discardable) code entry. 77 | * 78 | * @param {number} start The starting address. 79 | * @param {CodeMap.CodeEntry} codeEntry Code entry object. 80 | */ 81 | CodeMap.prototype.addCode = function(start, codeEntry) { 82 | this.deleteAllCoveredNodes_(this.dynamics_, start, start + codeEntry.size); 83 | this.dynamics_.insert(start, codeEntry); 84 | }; 85 | 86 | 87 | /** 88 | * Moves a dynamic code entry. Throws an exception if there is no dynamic 89 | * code entry with the specified starting address. 90 | * 91 | * @param {number} from The starting address of the entry being moved. 92 | * @param {number} to The destination address. 93 | */ 94 | CodeMap.prototype.moveCode = function(from, to) { 95 | var removedNode = this.dynamics_.remove(from); 96 | this.deleteAllCoveredNodes_(this.dynamics_, to, to + removedNode.value.size); 97 | this.dynamics_.insert(to, removedNode.value); 98 | }; 99 | 100 | 101 | /** 102 | * Discards a dynamic code entry. Throws an exception if there is no dynamic 103 | * code entry with the specified starting address. 104 | * 105 | * @param {number} start The starting address of the entry being deleted. 106 | */ 107 | CodeMap.prototype.deleteCode = function(start) { 108 | var removedNode = this.dynamics_.remove(start); 109 | }; 110 | 111 | 112 | /** 113 | * Adds a library entry. 114 | * 115 | * @param {number} start The starting address. 116 | * @param {CodeMap.CodeEntry} codeEntry Code entry object. 117 | */ 118 | CodeMap.prototype.addLibrary = function( 119 | start, codeEntry) { 120 | this.markPages_(start, start + codeEntry.size); 121 | this.libraries_.insert(start, codeEntry); 122 | }; 123 | 124 | 125 | /** 126 | * Adds a static code entry. 127 | * 128 | * @param {number} start The starting address. 129 | * @param {CodeMap.CodeEntry} codeEntry Code entry object. 130 | */ 131 | CodeMap.prototype.addStaticCode = function( 132 | start, codeEntry) { 133 | this.statics_.insert(start, codeEntry); 134 | }; 135 | 136 | 137 | /** 138 | * @private 139 | */ 140 | CodeMap.prototype.markPages_ = function(start, end) { 141 | for (var addr = start; addr <= end; 142 | addr += CodeMap.PAGE_SIZE) { 143 | this.pages_[addr >>> CodeMap.PAGE_ALIGNMENT] = 1; 144 | } 145 | }; 146 | 147 | 148 | /** 149 | * @private 150 | */ 151 | CodeMap.prototype.deleteAllCoveredNodes_ = function(tree, start, end) { 152 | var to_delete = []; 153 | var addr = end - 1; 154 | while (addr >= start) { 155 | var node = tree.findGreatestLessThan(addr); 156 | if (!node) break; 157 | var start2 = node.key, end2 = start2 + node.value.size; 158 | if (start2 < end && start < end2) to_delete.push(start2); 159 | addr = start2 - 1; 160 | } 161 | for (var i = 0, l = to_delete.length; i < l; ++i) tree.remove(to_delete[i]); 162 | }; 163 | 164 | 165 | /** 166 | * @private 167 | */ 168 | CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) { 169 | return addr >= node.key && addr < (node.key + node.value.size); 170 | }; 171 | 172 | 173 | /** 174 | * @private 175 | */ 176 | CodeMap.prototype.findInTree_ = function(tree, addr) { 177 | var node = tree.findGreatestLessThan(addr); 178 | return node && this.isAddressBelongsTo_(addr, node) ? node.value : null; 179 | }; 180 | 181 | 182 | /** 183 | * Finds a code entry that contains the specified address. Both static and 184 | * dynamic code entries are considered. 185 | * 186 | * @param {number} addr Address. 187 | */ 188 | CodeMap.prototype.findEntry = function(addr) { 189 | var pageAddr = addr >>> CodeMap.PAGE_ALIGNMENT; 190 | if (pageAddr in this.pages_) { 191 | // Static code entries can contain "holes" of unnamed code. 192 | // In this case, the whole library is assigned to this address. 193 | return this.findInTree_(this.statics_, addr) || 194 | this.findInTree_(this.libraries_, addr); 195 | } 196 | var min = this.dynamics_.findMin(); 197 | var max = this.dynamics_.findMax(); 198 | if (max != null && addr < (max.key + max.value.size) && addr >= min.key) { 199 | var dynaEntry = this.findInTree_(this.dynamics_, addr); 200 | if (dynaEntry == null) return null; 201 | // Dedupe entry name. 202 | if (!dynaEntry.nameUpdated_) { 203 | dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name); 204 | dynaEntry.nameUpdated_ = true; 205 | } 206 | return dynaEntry; 207 | } 208 | return null; 209 | }; 210 | 211 | 212 | /** 213 | * Returns a dynamic code entry using its starting address. 214 | * 215 | * @param {number} addr Address. 216 | */ 217 | CodeMap.prototype.findDynamicEntryByStartAddress = 218 | function(addr) { 219 | var node = this.dynamics_.find(addr); 220 | return node ? node.value : null; 221 | }; 222 | 223 | 224 | /** 225 | * Returns an array of all dynamic code entries. 226 | */ 227 | CodeMap.prototype.getAllDynamicEntries = function() { 228 | return this.dynamics_.exportValues(); 229 | }; 230 | 231 | 232 | /** 233 | * Returns an array of pairs of all dynamic code entries and their addresses. 234 | */ 235 | CodeMap.prototype.getAllDynamicEntriesWithAddresses = function() { 236 | return this.dynamics_.exportKeysAndValues(); 237 | }; 238 | 239 | 240 | /** 241 | * Returns an array of all static code entries. 242 | */ 243 | CodeMap.prototype.getAllStaticEntries = function() { 244 | return this.statics_.exportValues(); 245 | }; 246 | 247 | 248 | /** 249 | * Returns an array of all libraries entries. 250 | */ 251 | CodeMap.prototype.getAllLibrariesEntries = function() { 252 | return this.libraries_.exportValues(); 253 | }; 254 | 255 | 256 | /** 257 | * Creates a code entry object. 258 | * 259 | * @param {number} size Code entry size in bytes. 260 | * @param {string} opt_name Code entry name. 261 | * @constructor 262 | */ 263 | CodeMap.CodeEntry = function(size, opt_name) { 264 | this.size = size; 265 | this.name = opt_name || ''; 266 | this.nameUpdated_ = false; 267 | }; 268 | 269 | 270 | CodeMap.CodeEntry.prototype.getName = function() { 271 | return this.name; 272 | }; 273 | 274 | 275 | CodeMap.CodeEntry.prototype.toString = function() { 276 | return this.name + ': ' + this.size.toString(16); 277 | }; 278 | 279 | 280 | CodeMap.NameGenerator = function() { 281 | this.knownNames_ = {}; 282 | }; 283 | 284 | 285 | CodeMap.NameGenerator.prototype.getName = function(name) { 286 | if (!(name in this.knownNames_)) { 287 | this.knownNames_[name] = 0; 288 | return name; 289 | } 290 | var count = ++this.knownNames_[name]; 291 | return name + ' {' + count + '}'; 292 | }; 293 | -------------------------------------------------------------------------------- /tools/v8/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 | 94 | -------------------------------------------------------------------------------- /tools/v8/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 | -------------------------------------------------------------------------------- /tools/v8/logreader.js: -------------------------------------------------------------------------------- 1 | // Copyright 2011 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 | * @fileoverview Log Reader is used to process log file produced by V8. 30 | */ 31 | 32 | 33 | /** 34 | * Base class for processing log files. 35 | * 36 | * @param {Array.} dispatchTable A table used for parsing and processing 37 | * log records. 38 | * @constructor 39 | */ 40 | function LogReader(dispatchTable) { 41 | /** 42 | * @type {Array.} 43 | */ 44 | this.dispatchTable_ = dispatchTable; 45 | 46 | /** 47 | * Current line. 48 | * @type {number} 49 | */ 50 | this.lineNum_ = 0; 51 | 52 | /** 53 | * CSV lines parser. 54 | * @type {CsvParser} 55 | */ 56 | this.csvParser_ = new CsvParser(); 57 | }; 58 | 59 | 60 | /** 61 | * Used for printing error messages. 62 | * 63 | * @param {string} str Error message. 64 | */ 65 | LogReader.prototype.printError = function(str) { 66 | // Do nothing. 67 | }; 68 | 69 | 70 | /** 71 | * Processes a portion of V8 profiler event log. 72 | * 73 | * @param {string} chunk A portion of log. 74 | */ 75 | LogReader.prototype.processLogChunk = function(chunk) { 76 | this.processLog_(chunk.split('\n')); 77 | }; 78 | 79 | 80 | /** 81 | * Processes a line of V8 profiler event log. 82 | * 83 | * @param {string} line A line of log. 84 | */ 85 | LogReader.prototype.processLogLine = function(line) { 86 | this.processLog_([line]); 87 | }; 88 | 89 | 90 | /** 91 | * Processes stack record. 92 | * 93 | * @param {number} pc Program counter. 94 | * @param {number} func JS Function. 95 | * @param {Array.} stack String representation of a stack. 96 | * @return {Array.} Processed stack. 97 | */ 98 | LogReader.prototype.processStack = function(pc, func, stack) { 99 | var fullStack = func ? [pc, func] : [pc]; 100 | var prevFrame = pc; 101 | for (var i = 0, n = stack.length; i < n; ++i) { 102 | var frame = stack[i]; 103 | var firstChar = frame.charAt(0); 104 | if (firstChar == '+' || firstChar == '-') { 105 | // An offset from the previous frame. 106 | prevFrame += parseInt(frame, 16); 107 | fullStack.push(prevFrame); 108 | // Filter out possible 'overflow' string. 109 | } else if (firstChar != 'o') { 110 | fullStack.push(parseInt(frame, 16)); 111 | } 112 | } 113 | return fullStack; 114 | }; 115 | 116 | 117 | /** 118 | * Returns whether a particular dispatch must be skipped. 119 | * 120 | * @param {!Object} dispatch Dispatch record. 121 | * @return {boolean} True if dispatch must be skipped. 122 | */ 123 | LogReader.prototype.skipDispatch = function(dispatch) { 124 | return false; 125 | }; 126 | 127 | 128 | /** 129 | * Does a dispatch of a log record. 130 | * 131 | * @param {Array.} fields Log record. 132 | * @private 133 | */ 134 | LogReader.prototype.dispatchLogRow_ = function(fields) { 135 | // Obtain the dispatch. 136 | var command = fields[0]; 137 | if (!(command in this.dispatchTable_)) return; 138 | 139 | var dispatch = this.dispatchTable_[command]; 140 | 141 | if (dispatch === null || this.skipDispatch(dispatch)) { 142 | return; 143 | } 144 | 145 | // Parse fields. 146 | var parsedFields = []; 147 | for (var i = 0; i < dispatch.parsers.length; ++i) { 148 | var parser = dispatch.parsers[i]; 149 | if (parser === null) { 150 | parsedFields.push(fields[1 + i]); 151 | } else if (typeof parser == 'function') { 152 | parsedFields.push(parser(fields[1 + i])); 153 | } else { 154 | // var-args 155 | parsedFields.push(fields.slice(1 + i)); 156 | break; 157 | } 158 | } 159 | 160 | // Run the processor. 161 | dispatch.processor.apply(this, parsedFields); 162 | }; 163 | 164 | 165 | /** 166 | * Processes log lines. 167 | * 168 | * @param {Array.} lines Log lines. 169 | * @private 170 | */ 171 | LogReader.prototype.processLog_ = function(lines) { 172 | for (var i = 0, n = lines.length; i < n; ++i, ++this.lineNum_) { 173 | var line = lines[i]; 174 | if (!line) { 175 | continue; 176 | } 177 | try { 178 | var fields = this.csvParser_.parseLine(line); 179 | this.dispatchLogRow_(fields); 180 | } catch (e) { 181 | this.printError('line ' + (this.lineNum_ + 1) + ': ' + (e.message || e)); 182 | } 183 | } 184 | }; 185 | -------------------------------------------------------------------------------- /tools/v8/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 | 29 | /** 30 | * Creates a profile object for processing profiling-related events 31 | * and calculating function execution times. 32 | * 33 | * @constructor 34 | */ 35 | function Profile() { 36 | this.codeMap_ = new CodeMap(); 37 | this.topDownTree_ = new CallTree(); 38 | this.bottomUpTree_ = new CallTree(); 39 | }; 40 | 41 | 42 | /** 43 | * Returns whether a function with the specified name must be skipped. 44 | * Should be overriden by subclasses. 45 | * 46 | * @param {string} name Function name. 47 | */ 48 | Profile.prototype.skipThisFunction = function(name) { 49 | return false; 50 | }; 51 | 52 | 53 | /** 54 | * Enum for profiler operations that involve looking up existing 55 | * code entries. 56 | * 57 | * @enum {number} 58 | */ 59 | Profile.Operation = { 60 | MOVE: 0, 61 | DELETE: 1, 62 | TICK: 2 63 | }; 64 | 65 | 66 | /** 67 | * Enum for code state regarding its dynamic optimization. 68 | * 69 | * @enum {number} 70 | */ 71 | Profile.CodeState = { 72 | COMPILED: 0, 73 | OPTIMIZABLE: 1, 74 | OPTIMIZED: 2 75 | }; 76 | 77 | 78 | /** 79 | * Called whenever the specified operation has failed finding a function 80 | * containing the specified address. Should be overriden by subclasses. 81 | * See the Profile.Operation enum for the list of 82 | * possible operations. 83 | * 84 | * @param {number} operation Operation. 85 | * @param {number} addr Address of the unknown code. 86 | * @param {number} opt_stackPos If an unknown address is encountered 87 | * during stack strace processing, specifies a position of the frame 88 | * containing the address. 89 | */ 90 | Profile.prototype.handleUnknownCode = function( 91 | operation, addr, opt_stackPos) { 92 | }; 93 | 94 | 95 | /** 96 | * Registers a library. 97 | * 98 | * @param {string} name Code entry name. 99 | * @param {number} startAddr Starting address. 100 | * @param {number} endAddr Ending address. 101 | */ 102 | Profile.prototype.addLibrary = function( 103 | name, startAddr, endAddr) { 104 | var entry = new CodeMap.CodeEntry( 105 | endAddr - startAddr, name); 106 | this.codeMap_.addLibrary(startAddr, entry); 107 | return entry; 108 | }; 109 | 110 | 111 | /** 112 | * Registers statically compiled code entry. 113 | * 114 | * @param {string} name Code entry name. 115 | * @param {number} startAddr Starting address. 116 | * @param {number} endAddr Ending address. 117 | */ 118 | Profile.prototype.addStaticCode = function( 119 | name, startAddr, endAddr) { 120 | var entry = new CodeMap.CodeEntry( 121 | endAddr - startAddr, name); 122 | this.codeMap_.addStaticCode(startAddr, entry); 123 | return entry; 124 | }; 125 | 126 | 127 | /** 128 | * Registers dynamic (JIT-compiled) code entry. 129 | * 130 | * @param {string} type Code entry type. 131 | * @param {string} name Code entry name. 132 | * @param {number} start Starting address. 133 | * @param {number} size Code entry size. 134 | */ 135 | Profile.prototype.addCode = function( 136 | type, name, start, size) { 137 | var entry = new Profile.DynamicCodeEntry(size, type, name); 138 | this.codeMap_.addCode(start, entry); 139 | return entry; 140 | }; 141 | 142 | 143 | /** 144 | * Registers dynamic (JIT-compiled) code entry. 145 | * 146 | * @param {string} type Code entry type. 147 | * @param {string} name Code entry name. 148 | * @param {number} start Starting address. 149 | * @param {number} size Code entry size. 150 | * @param {number} funcAddr Shared function object address. 151 | * @param {Profile.CodeState} state Optimization state. 152 | */ 153 | Profile.prototype.addFuncCode = function( 154 | type, name, start, size, funcAddr, state) { 155 | // As code and functions are in the same address space, 156 | // it is safe to put them in a single code map. 157 | var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr); 158 | if (!func) { 159 | func = new Profile.FunctionEntry(name); 160 | this.codeMap_.addCode(funcAddr, func); 161 | } else if (func.name !== name) { 162 | // Function object has been overwritten with a new one. 163 | func.name = name; 164 | } 165 | var entry = this.codeMap_.findDynamicEntryByStartAddress(start); 166 | if (entry) { 167 | if (entry.size === size && entry.func === func) { 168 | // Entry state has changed. 169 | entry.state = state; 170 | } 171 | } else { 172 | entry = new Profile.DynamicFuncCodeEntry(size, type, func, state); 173 | this.codeMap_.addCode(start, entry); 174 | } 175 | return entry; 176 | }; 177 | 178 | 179 | /** 180 | * Reports about moving of a dynamic code entry. 181 | * 182 | * @param {number} from Current code entry address. 183 | * @param {number} to New code entry address. 184 | */ 185 | Profile.prototype.moveCode = function(from, to) { 186 | try { 187 | this.codeMap_.moveCode(from, to); 188 | } catch (e) { 189 | this.handleUnknownCode(Profile.Operation.MOVE, from); 190 | } 191 | }; 192 | 193 | 194 | /** 195 | * Reports about deletion of a dynamic code entry. 196 | * 197 | * @param {number} start Starting address. 198 | */ 199 | Profile.prototype.deleteCode = function(start) { 200 | try { 201 | this.codeMap_.deleteCode(start); 202 | } catch (e) { 203 | this.handleUnknownCode(Profile.Operation.DELETE, start); 204 | } 205 | }; 206 | 207 | 208 | /** 209 | * Reports about moving of a dynamic code entry. 210 | * 211 | * @param {number} from Current code entry address. 212 | * @param {number} to New code entry address. 213 | */ 214 | Profile.prototype.moveFunc = function(from, to) { 215 | if (this.codeMap_.findDynamicEntryByStartAddress(from)) { 216 | this.codeMap_.moveCode(from, to); 217 | } 218 | }; 219 | 220 | 221 | /** 222 | * Retrieves a code entry by an address. 223 | * 224 | * @param {number} addr Entry address. 225 | */ 226 | Profile.prototype.findEntry = function(addr) { 227 | return this.codeMap_.findEntry(addr); 228 | }; 229 | 230 | 231 | /** 232 | * Records a tick event. Stack must contain a sequence of 233 | * addresses starting with the program counter value. 234 | * 235 | * @param {Array} stack Stack sample. 236 | */ 237 | Profile.prototype.recordTick = function(stack) { 238 | var processedStack = this.resolveAndFilterFuncs_(stack); 239 | this.bottomUpTree_.addPath(processedStack); 240 | processedStack.reverse(); 241 | this.topDownTree_.addPath(processedStack); 242 | }; 243 | 244 | 245 | /** 246 | * Translates addresses into function names and filters unneeded 247 | * functions. 248 | * 249 | * @param {Array} stack Stack sample. 250 | */ 251 | Profile.prototype.resolveAndFilterFuncs_ = function(stack) { 252 | var result = []; 253 | for (var i = 0; i < stack.length; ++i) { 254 | var entry = this.codeMap_.findEntry(stack[i]); 255 | if (entry) { 256 | var name = entry.getName(); 257 | if (!this.skipThisFunction(name)) { 258 | result.push(name); 259 | } 260 | } else { 261 | this.handleUnknownCode( 262 | Profile.Operation.TICK, stack[i], i); 263 | } 264 | } 265 | return result; 266 | }; 267 | 268 | 269 | /** 270 | * Performs a BF traversal of the top down call graph. 271 | * 272 | * @param {function(CallTree.Node)} f Visitor function. 273 | */ 274 | Profile.prototype.traverseTopDownTree = function(f) { 275 | this.topDownTree_.traverse(f); 276 | }; 277 | 278 | 279 | /** 280 | * Performs a BF traversal of the bottom up call graph. 281 | * 282 | * @param {function(CallTree.Node)} f Visitor function. 283 | */ 284 | Profile.prototype.traverseBottomUpTree = function(f) { 285 | this.bottomUpTree_.traverse(f); 286 | }; 287 | 288 | 289 | /** 290 | * Calculates a top down profile for a node with the specified label. 291 | * If no name specified, returns the whole top down calls tree. 292 | * 293 | * @param {string} opt_label Node label. 294 | */ 295 | Profile.prototype.getTopDownProfile = function(opt_label) { 296 | return this.getTreeProfile_(this.topDownTree_, opt_label); 297 | }; 298 | 299 | 300 | /** 301 | * Calculates a bottom up profile for a node with the specified label. 302 | * If no name specified, returns the whole bottom up calls tree. 303 | * 304 | * @param {string} opt_label Node label. 305 | */ 306 | Profile.prototype.getBottomUpProfile = function(opt_label) { 307 | return this.getTreeProfile_(this.bottomUpTree_, opt_label); 308 | }; 309 | 310 | 311 | /** 312 | * Helper function for calculating a tree profile. 313 | * 314 | * @param {Profile.CallTree} tree Call tree. 315 | * @param {string} opt_label Node label. 316 | */ 317 | Profile.prototype.getTreeProfile_ = function(tree, opt_label) { 318 | if (!opt_label) { 319 | tree.computeTotalWeights(); 320 | return tree; 321 | } else { 322 | var subTree = tree.cloneSubtree(opt_label); 323 | subTree.computeTotalWeights(); 324 | return subTree; 325 | } 326 | }; 327 | 328 | 329 | /** 330 | * Calculates a flat profile of callees starting from a node with 331 | * the specified label. If no name specified, starts from the root. 332 | * 333 | * @param {string} opt_label Starting node label. 334 | */ 335 | Profile.prototype.getFlatProfile = function(opt_label) { 336 | var counters = new CallTree(); 337 | var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL; 338 | var precs = {}; 339 | precs[rootLabel] = 0; 340 | var root = counters.findOrAddChild(rootLabel); 341 | 342 | this.topDownTree_.computeTotalWeights(); 343 | this.topDownTree_.traverseInDepth( 344 | function onEnter(node) { 345 | if (!(node.label in precs)) { 346 | precs[node.label] = 0; 347 | } 348 | var nodeLabelIsRootLabel = node.label == rootLabel; 349 | if (nodeLabelIsRootLabel || precs[rootLabel] > 0) { 350 | if (precs[rootLabel] == 0) { 351 | root.selfWeight += node.selfWeight; 352 | root.totalWeight += node.totalWeight; 353 | } else { 354 | var rec = root.findOrAddChild(node.label); 355 | rec.selfWeight += node.selfWeight; 356 | if (nodeLabelIsRootLabel || precs[node.label] == 0) { 357 | rec.totalWeight += node.totalWeight; 358 | } 359 | } 360 | precs[node.label]++; 361 | } 362 | }, 363 | function onExit(node) { 364 | if (node.label == rootLabel || precs[rootLabel] > 0) { 365 | precs[node.label]--; 366 | } 367 | }, 368 | null); 369 | 370 | if (!opt_label) { 371 | // If we have created a flat profile for the whole program, we don't 372 | // need an explicit root in it. Thus, replace the counters tree 373 | // root with the node corresponding to the whole program. 374 | counters.root_ = root; 375 | } else { 376 | // Propagate weights so percents can be calculated correctly. 377 | counters.getRoot().selfWeight = root.selfWeight; 378 | counters.getRoot().totalWeight = root.totalWeight; 379 | } 380 | return counters; 381 | }; 382 | 383 | 384 | /** 385 | * Cleans up function entries that are not referenced by code entries. 386 | */ 387 | Profile.prototype.cleanUpFuncEntries = function() { 388 | var referencedFuncEntries = []; 389 | var entries = this.codeMap_.getAllDynamicEntriesWithAddresses(); 390 | for (var i = 0, l = entries.length; i < l; ++i) { 391 | if (entries[i][1].constructor === Profile.FunctionEntry) { 392 | entries[i][1].used = false; 393 | } 394 | } 395 | for (var i = 0, l = entries.length; i < l; ++i) { 396 | if ("func" in entries[i][1]) { 397 | entries[i][1].func.used = true; 398 | } 399 | } 400 | for (var i = 0, l = entries.length; i < l; ++i) { 401 | if (entries[i][1].constructor === Profile.FunctionEntry && 402 | !entries[i][1].used) { 403 | this.codeMap_.deleteCode(entries[i][0]); 404 | } 405 | } 406 | }; 407 | 408 | 409 | /** 410 | * Creates a dynamic code entry. 411 | * 412 | * @param {number} size Code size. 413 | * @param {string} type Code type. 414 | * @param {string} name Function name. 415 | * @constructor 416 | */ 417 | Profile.DynamicCodeEntry = function(size, type, name) { 418 | CodeMap.CodeEntry.call(this, size, name); 419 | this.type = type; 420 | }; 421 | 422 | 423 | /** 424 | * Returns node name. 425 | */ 426 | Profile.DynamicCodeEntry.prototype.getName = function() { 427 | return this.type + ': ' + this.name; 428 | }; 429 | 430 | 431 | /** 432 | * Returns raw node name (without type decoration). 433 | */ 434 | Profile.DynamicCodeEntry.prototype.getRawName = function() { 435 | return this.name; 436 | }; 437 | 438 | 439 | Profile.DynamicCodeEntry.prototype.isJSFunction = function() { 440 | return false; 441 | }; 442 | 443 | 444 | Profile.DynamicCodeEntry.prototype.toString = function() { 445 | return this.getName() + ': ' + this.size.toString(16); 446 | }; 447 | 448 | 449 | /** 450 | * Creates a dynamic code entry. 451 | * 452 | * @param {number} size Code size. 453 | * @param {string} type Code type. 454 | * @param {Profile.FunctionEntry} func Shared function entry. 455 | * @param {Profile.CodeState} state Code optimization state. 456 | * @constructor 457 | */ 458 | Profile.DynamicFuncCodeEntry = function(size, type, func, state) { 459 | CodeMap.CodeEntry.call(this, size); 460 | this.type = type; 461 | this.func = func; 462 | this.state = state; 463 | }; 464 | 465 | Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"]; 466 | 467 | /** 468 | * Returns node name. 469 | */ 470 | Profile.DynamicFuncCodeEntry.prototype.getName = function() { 471 | var name = this.func.getName(); 472 | return this.type + ': ' + Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + name; 473 | }; 474 | 475 | 476 | /** 477 | * Returns raw node name (without type decoration). 478 | */ 479 | Profile.DynamicFuncCodeEntry.prototype.getRawName = function() { 480 | return this.func.getName(); 481 | }; 482 | 483 | 484 | Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() { 485 | return true; 486 | }; 487 | 488 | 489 | Profile.DynamicFuncCodeEntry.prototype.toString = function() { 490 | return this.getName() + ': ' + this.size.toString(16); 491 | }; 492 | 493 | 494 | /** 495 | * Creates a shared function object entry. 496 | * 497 | * @param {string} name Function name. 498 | * @constructor 499 | */ 500 | Profile.FunctionEntry = function(name) { 501 | CodeMap.CodeEntry.call(this, 0, name); 502 | }; 503 | 504 | 505 | /** 506 | * Returns node name. 507 | */ 508 | Profile.FunctionEntry.prototype.getName = function() { 509 | var name = this.name; 510 | if (name.length == 0) { 511 | name = ''; 512 | } else if (name.charAt(0) == ' ') { 513 | // An anonymous function with location: " aaa.js:10". 514 | name = '' + name; 515 | } 516 | return name; 517 | }; 518 | 519 | Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString; 520 | 521 | /** 522 | * Constructs a call graph. 523 | * 524 | * @constructor 525 | */ 526 | function CallTree() { 527 | this.root_ = new CallTree.Node( 528 | CallTree.ROOT_NODE_LABEL); 529 | }; 530 | 531 | 532 | /** 533 | * The label of the root node. 534 | */ 535 | CallTree.ROOT_NODE_LABEL = ''; 536 | 537 | 538 | /** 539 | * @private 540 | */ 541 | CallTree.prototype.totalsComputed_ = false; 542 | 543 | 544 | /** 545 | * Returns the tree root. 546 | */ 547 | CallTree.prototype.getRoot = function() { 548 | return this.root_; 549 | }; 550 | 551 | 552 | /** 553 | * Adds the specified call path, constructing nodes as necessary. 554 | * 555 | * @param {Array} path Call path. 556 | */ 557 | CallTree.prototype.addPath = function(path) { 558 | if (path.length == 0) { 559 | return; 560 | } 561 | var curr = this.root_; 562 | for (var i = 0; i < path.length; ++i) { 563 | curr = curr.findOrAddChild(path[i]); 564 | } 565 | curr.selfWeight++; 566 | this.totalsComputed_ = false; 567 | }; 568 | 569 | 570 | /** 571 | * Finds an immediate child of the specified parent with the specified 572 | * label, creates a child node if necessary. If a parent node isn't 573 | * specified, uses tree root. 574 | * 575 | * @param {string} label Child node label. 576 | */ 577 | CallTree.prototype.findOrAddChild = function(label) { 578 | return this.root_.findOrAddChild(label); 579 | }; 580 | 581 | 582 | /** 583 | * Creates a subtree by cloning and merging all subtrees rooted at nodes 584 | * with a given label. E.g. cloning the following call tree on label 'A' 585 | * will give the following result: 586 | * 587 | * -- 588 | * / / 589 | * == clone on 'A' ==> -- 590 | * \ \ 591 | * ---- 592 | * 593 | * And 's selfWeight will be the sum of selfWeights of 's from the 594 | * source call tree. 595 | * 596 | * @param {string} label The label of the new root node. 597 | */ 598 | CallTree.prototype.cloneSubtree = function(label) { 599 | var subTree = new CallTree(); 600 | this.traverse(function(node, parent) { 601 | if (!parent && node.label != label) { 602 | return null; 603 | } 604 | var child = (parent ? parent : subTree).findOrAddChild(node.label); 605 | child.selfWeight += node.selfWeight; 606 | return child; 607 | }); 608 | return subTree; 609 | }; 610 | 611 | 612 | /** 613 | * Computes total weights in the call graph. 614 | */ 615 | CallTree.prototype.computeTotalWeights = function() { 616 | if (this.totalsComputed_) { 617 | return; 618 | } 619 | this.root_.computeTotalWeight(); 620 | this.totalsComputed_ = true; 621 | }; 622 | 623 | 624 | /** 625 | * Traverses the call graph in preorder. This function can be used for 626 | * building optionally modified tree clones. This is the boilerplate code 627 | * for this scenario: 628 | * 629 | * callTree.traverse(function(node, parentClone) { 630 | * var nodeClone = cloneNode(node); 631 | * if (parentClone) 632 | * parentClone.addChild(nodeClone); 633 | * return nodeClone; 634 | * }); 635 | * 636 | * @param {function(CallTree.Node, *)} f Visitor function. 637 | * The second parameter is the result of calling 'f' on the parent node. 638 | */ 639 | CallTree.prototype.traverse = function(f) { 640 | var pairsToProcess = new ConsArray(); 641 | pairsToProcess.concat([{node: this.root_, param: null}]); 642 | while (!pairsToProcess.atEnd()) { 643 | var pair = pairsToProcess.next(); 644 | var node = pair.node; 645 | var newParam = f(node, pair.param); 646 | var morePairsToProcess = []; 647 | node.forEachChild(function (child) { 648 | morePairsToProcess.push({node: child, param: newParam}); }); 649 | pairsToProcess.concat(morePairsToProcess); 650 | } 651 | }; 652 | 653 | 654 | /** 655 | * Performs an indepth call graph traversal. 656 | * 657 | * @param {function(CallTree.Node)} enter A function called 658 | * prior to visiting node's children. 659 | * @param {function(CallTree.Node)} exit A function called 660 | * after visiting node's children. 661 | */ 662 | CallTree.prototype.traverseInDepth = function(enter, exit) { 663 | function traverse(node) { 664 | enter(node); 665 | node.forEachChild(traverse); 666 | exit(node); 667 | } 668 | traverse(this.root_); 669 | }; 670 | 671 | 672 | /** 673 | * Constructs a call graph node. 674 | * 675 | * @param {string} label Node label. 676 | * @param {CallTree.Node} opt_parent Node parent. 677 | */ 678 | CallTree.Node = function(label, opt_parent) { 679 | this.label = label; 680 | this.parent = opt_parent; 681 | this.children = {}; 682 | }; 683 | 684 | 685 | /** 686 | * Node self weight (how many times this node was the last node in 687 | * a call path). 688 | * @type {number} 689 | */ 690 | CallTree.Node.prototype.selfWeight = 0; 691 | 692 | 693 | /** 694 | * Node total weight (includes weights of all children). 695 | * @type {number} 696 | */ 697 | CallTree.Node.prototype.totalWeight = 0; 698 | 699 | 700 | /** 701 | * Adds a child node. 702 | * 703 | * @param {string} label Child node label. 704 | */ 705 | CallTree.Node.prototype.addChild = function(label) { 706 | var child = new CallTree.Node(label, this); 707 | this.children[label] = child; 708 | return child; 709 | }; 710 | 711 | 712 | /** 713 | * Computes node's total weight. 714 | */ 715 | CallTree.Node.prototype.computeTotalWeight = 716 | function() { 717 | var totalWeight = this.selfWeight; 718 | this.forEachChild(function(child) { 719 | totalWeight += child.computeTotalWeight(); }); 720 | return this.totalWeight = totalWeight; 721 | }; 722 | 723 | 724 | /** 725 | * Returns all node's children as an array. 726 | */ 727 | CallTree.Node.prototype.exportChildren = function() { 728 | var result = []; 729 | this.forEachChild(function (node) { result.push(node); }); 730 | return result; 731 | }; 732 | 733 | 734 | /** 735 | * Finds an immediate child with the specified label. 736 | * 737 | * @param {string} label Child node label. 738 | */ 739 | CallTree.Node.prototype.findChild = function(label) { 740 | return this.children[label] || null; 741 | }; 742 | 743 | 744 | /** 745 | * Finds an immediate child with the specified label, creates a child 746 | * node if necessary. 747 | * 748 | * @param {string} label Child node label. 749 | */ 750 | CallTree.Node.prototype.findOrAddChild = function(label) { 751 | return this.findChild(label) || this.addChild(label); 752 | }; 753 | 754 | 755 | /** 756 | * Calls the specified function for every child. 757 | * 758 | * @param {function(CallTree.Node)} f Visitor function. 759 | */ 760 | CallTree.Node.prototype.forEachChild = function(f) { 761 | for (var c in this.children) { 762 | f(this.children[c]); 763 | } 764 | }; 765 | 766 | 767 | /** 768 | * Walks up from the current node up to the call tree root. 769 | * 770 | * @param {function(CallTree.Node)} f Visitor function. 771 | */ 772 | CallTree.Node.prototype.walkUpToRoot = function(f) { 773 | for (var curr = this; curr != null; curr = curr.parent) { 774 | f(curr); 775 | } 776 | }; 777 | 778 | 779 | /** 780 | * Tries to find a node with the specified path. 781 | * 782 | * @param {Array} labels The path. 783 | * @param {function(CallTree.Node)} opt_f Visitor function. 784 | */ 785 | CallTree.Node.prototype.descendToChild = function( 786 | labels, opt_f) { 787 | for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) { 788 | var child = curr.findChild(labels[pos]); 789 | if (opt_f) { 790 | opt_f(child, pos); 791 | } 792 | curr = child; 793 | } 794 | return curr; 795 | }; 796 | -------------------------------------------------------------------------------- /tools/v8/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 | 29 | /** 30 | * Creates a Profile View builder object. 31 | * 32 | * @param {number} samplingRate Number of ms between profiler ticks. 33 | * @constructor 34 | */ 35 | function ViewBuilder(samplingRate) { 36 | this.samplingRate = samplingRate; 37 | }; 38 | 39 | 40 | /** 41 | * Builds a profile view for the specified call tree. 42 | * 43 | * @param {CallTree} callTree A call tree. 44 | * @param {boolean} opt_bottomUpViewWeights Whether remapping 45 | * of self weights for a bottom up view is needed. 46 | */ 47 | ViewBuilder.prototype.buildView = function( 48 | callTree, opt_bottomUpViewWeights) { 49 | var head; 50 | var samplingRate = this.samplingRate; 51 | var createViewNode = this.createViewNode; 52 | callTree.traverse(function(node, viewParent) { 53 | var totalWeight = node.totalWeight * samplingRate; 54 | var selfWeight = node.selfWeight * samplingRate; 55 | if (opt_bottomUpViewWeights === true) { 56 | if (viewParent === head) { 57 | selfWeight = totalWeight; 58 | } else { 59 | selfWeight = 0; 60 | } 61 | } 62 | var viewNode = createViewNode(node.label, totalWeight, selfWeight, head); 63 | if (viewParent) { 64 | viewParent.addChild(viewNode); 65 | } else { 66 | head = viewNode; 67 | } 68 | return viewNode; 69 | }); 70 | var view = this.createView(head); 71 | return view; 72 | }; 73 | 74 | 75 | /** 76 | * Factory method for a profile view. 77 | * 78 | * @param {ProfileView.Node} head View head node. 79 | * @return {ProfileView} Profile view. 80 | */ 81 | ViewBuilder.prototype.createView = function(head) { 82 | return new ProfileView(head); 83 | }; 84 | 85 | 86 | /** 87 | * Factory method for a profile view node. 88 | * 89 | * @param {string} internalFuncName A fully qualified function name. 90 | * @param {number} totalTime Amount of time that application spent in the 91 | * corresponding function and its descendants (not that depending on 92 | * profile they can be either callees or callers.) 93 | * @param {number} selfTime Amount of time that application spent in the 94 | * corresponding function only. 95 | * @param {ProfileView.Node} head Profile view head. 96 | * @return {ProfileView.Node} Profile view node. 97 | */ 98 | ViewBuilder.prototype.createViewNode = function( 99 | funcName, totalTime, selfTime, head) { 100 | return new ProfileView.Node( 101 | funcName, totalTime, selfTime, head); 102 | }; 103 | 104 | 105 | /** 106 | * Creates a Profile View object. It allows to perform sorting 107 | * and filtering actions on the profile. 108 | * 109 | * @param {ProfileView.Node} head Head (root) node. 110 | * @constructor 111 | */ 112 | function ProfileView(head) { 113 | this.head = head; 114 | }; 115 | 116 | 117 | /** 118 | * Sorts the profile view using the specified sort function. 119 | * 120 | * @param {function(ProfileView.Node, 121 | * ProfileView.Node):number} sortFunc A sorting 122 | * functions. Must comply with Array.sort sorting function requirements. 123 | */ 124 | ProfileView.prototype.sort = function(sortFunc) { 125 | this.traverse(function (node) { 126 | node.sortChildren(sortFunc); 127 | }); 128 | }; 129 | 130 | 131 | /** 132 | * Traverses profile view nodes in preorder. 133 | * 134 | * @param {function(ProfileView.Node)} f Visitor function. 135 | */ 136 | ProfileView.prototype.traverse = function(f) { 137 | var nodesToTraverse = new ConsArray(); 138 | nodesToTraverse.concat([this.head]); 139 | while (!nodesToTraverse.atEnd()) { 140 | var node = nodesToTraverse.next(); 141 | f(node); 142 | nodesToTraverse.concat(node.children); 143 | } 144 | }; 145 | 146 | 147 | /** 148 | * Constructs a Profile View node object. Each node object corresponds to 149 | * a function call. 150 | * 151 | * @param {string} internalFuncName A fully qualified function name. 152 | * @param {number} totalTime Amount of time that application spent in the 153 | * corresponding function and its descendants (not that depending on 154 | * profile they can be either callees or callers.) 155 | * @param {number} selfTime Amount of time that application spent in the 156 | * corresponding function only. 157 | * @param {ProfileView.Node} head Profile view head. 158 | * @constructor 159 | */ 160 | ProfileView.Node = function( 161 | internalFuncName, totalTime, selfTime, head) { 162 | this.internalFuncName = internalFuncName; 163 | this.totalTime = totalTime; 164 | this.selfTime = selfTime; 165 | this.head = head; 166 | this.parent = null; 167 | this.children = []; 168 | }; 169 | 170 | 171 | /** 172 | * Returns a share of the function's total time in application's total time. 173 | */ 174 | ProfileView.Node.prototype.__defineGetter__( 175 | 'totalPercent', 176 | function() { return this.totalTime / 177 | (this.head ? this.head.totalTime : this.totalTime) * 100.0; }); 178 | 179 | 180 | /** 181 | * Returns a share of the function's self time in application's total time. 182 | */ 183 | ProfileView.Node.prototype.__defineGetter__( 184 | 'selfPercent', 185 | function() { return this.selfTime / 186 | (this.head ? this.head.totalTime : this.totalTime) * 100.0; }); 187 | 188 | 189 | /** 190 | * Returns a share of the function's total time in its parent's total time. 191 | */ 192 | ProfileView.Node.prototype.__defineGetter__( 193 | 'parentTotalPercent', 194 | function() { return this.totalTime / 195 | (this.parent ? this.parent.totalTime : this.totalTime) * 100.0; }); 196 | 197 | 198 | /** 199 | * Adds a child to the node. 200 | * 201 | * @param {ProfileView.Node} node Child node. 202 | */ 203 | ProfileView.Node.prototype.addChild = function(node) { 204 | node.parent = this; 205 | this.children.push(node); 206 | }; 207 | 208 | 209 | /** 210 | * Sorts all the node's children recursively. 211 | * 212 | * @param {function(ProfileView.Node, 213 | * ProfileView.Node):number} sortFunc A sorting 214 | * functions. Must comply with Array.sort sorting function requirements. 215 | */ 216 | ProfileView.Node.prototype.sortChildren = function( 217 | sortFunc) { 218 | this.children.sort(sortFunc); 219 | }; 220 | -------------------------------------------------------------------------------- /tools/v8/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 | -------------------------------------------------------------------------------- /tools/v8/tickprocessor-driver.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 | 29 | // Tick Processor's code flow. 30 | 31 | function processArguments(args) { 32 | var processor = new ArgumentsProcessor(args); 33 | if (processor.parse()) { 34 | return processor.result(); 35 | } else { 36 | processor.printUsageAndExit(); 37 | } 38 | } 39 | 40 | var entriesProviders = { 41 | 'unix': UnixCppEntriesProvider, 42 | 'windows': WindowsCppEntriesProvider, 43 | 'mac': MacCppEntriesProvider 44 | }; 45 | 46 | var params = processArguments(arguments); 47 | var snapshotLogProcessor; 48 | if (params.snapshotLogFileName) { 49 | snapshotLogProcessor = new SnapshotLogProcessor(); 50 | snapshotLogProcessor.processLogFile(params.snapshotLogFileName); 51 | } 52 | var tickProcessor = new TickProcessor( 53 | new (entriesProviders[params.platform])(params.nm, params.targetRootFS), 54 | params.separateIc, 55 | params.callGraphSize, 56 | params.ignoreUnknown, 57 | params.stateFilter, 58 | snapshotLogProcessor); 59 | tickProcessor.processLogFile(params.logFileName); 60 | tickProcessor.printStatistics(); 61 | -------------------------------------------------------------------------------- /tools/v8/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 | 29 | function inherits(childCtor, parentCtor) { 30 | childCtor.prototype.__proto__ = parentCtor.prototype; 31 | }; 32 | 33 | 34 | function V8Profile(separateIc) { 35 | Profile.call(this); 36 | if (!separateIc) { 37 | this.skipThisFunction = function(name) { return V8Profile.IC_RE.test(name); }; 38 | } 39 | }; 40 | inherits(V8Profile, Profile); 41 | 42 | 43 | V8Profile.IC_RE = 44 | /^(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Call|Load|Store)IC_)/; 45 | 46 | 47 | /** 48 | * A thin wrapper around shell's 'read' function showing a file name on error. 49 | */ 50 | function readFile(fileName) { 51 | try { 52 | return read(fileName); 53 | } catch (e) { 54 | print(fileName + ': ' + (e.message || e)); 55 | throw e; 56 | } 57 | } 58 | 59 | 60 | /** 61 | * Parser for dynamic code optimization state. 62 | */ 63 | function parseState(s) { 64 | switch (s) { 65 | case "": return Profile.CodeState.COMPILED; 66 | case "~": return Profile.CodeState.OPTIMIZABLE; 67 | case "*": return Profile.CodeState.OPTIMIZED; 68 | } 69 | throw new Error("unknown code state: " + s); 70 | } 71 | 72 | 73 | function SnapshotLogProcessor() { 74 | LogReader.call(this, { 75 | 'code-creation': { 76 | parsers: [null, parseInt, parseInt, null, 'var-args'], 77 | processor: this.processCodeCreation }, 78 | 'code-move': { parsers: [parseInt, parseInt], 79 | processor: this.processCodeMove }, 80 | 'code-delete': { parsers: [parseInt], 81 | processor: this.processCodeDelete }, 82 | 'function-creation': null, 83 | 'function-move': null, 84 | 'function-delete': null, 85 | 'sfi-move': null, 86 | 'snapshot-pos': { parsers: [parseInt, parseInt], 87 | processor: this.processSnapshotPosition }}); 88 | 89 | V8Profile.prototype.handleUnknownCode = function(operation, addr) { 90 | var op = Profile.Operation; 91 | switch (operation) { 92 | case op.MOVE: 93 | print('Snapshot: Code move event for unknown code: 0x' + 94 | addr.toString(16)); 95 | break; 96 | case op.DELETE: 97 | print('Snapshot: Code delete event for unknown code: 0x' + 98 | addr.toString(16)); 99 | break; 100 | } 101 | }; 102 | 103 | this.profile_ = new V8Profile(); 104 | this.serializedEntries_ = []; 105 | } 106 | inherits(SnapshotLogProcessor, LogReader); 107 | 108 | 109 | SnapshotLogProcessor.prototype.processCodeCreation = function( 110 | type, start, size, name, maybe_func) { 111 | if (maybe_func.length) { 112 | var funcAddr = parseInt(maybe_func[0]); 113 | var state = parseState(maybe_func[1]); 114 | this.profile_.addFuncCode(type, name, start, size, funcAddr, state); 115 | } else { 116 | this.profile_.addCode(type, name, start, size); 117 | } 118 | }; 119 | 120 | 121 | SnapshotLogProcessor.prototype.processCodeMove = function(from, to) { 122 | this.profile_.moveCode(from, to); 123 | }; 124 | 125 | 126 | SnapshotLogProcessor.prototype.processCodeDelete = function(start) { 127 | this.profile_.deleteCode(start); 128 | }; 129 | 130 | 131 | SnapshotLogProcessor.prototype.processSnapshotPosition = function(addr, pos) { 132 | this.serializedEntries_[pos] = this.profile_.findEntry(addr); 133 | }; 134 | 135 | 136 | SnapshotLogProcessor.prototype.processLogFile = function(fileName) { 137 | var contents = readFile(fileName); 138 | this.processLogChunk(contents); 139 | }; 140 | 141 | 142 | SnapshotLogProcessor.prototype.getSerializedEntryName = function(pos) { 143 | var entry = this.serializedEntries_[pos]; 144 | return entry ? entry.getRawName() : null; 145 | }; 146 | 147 | 148 | function TickProcessor( 149 | cppEntriesProvider, 150 | separateIc, 151 | callGraphSize, 152 | ignoreUnknown, 153 | stateFilter, 154 | snapshotLogProcessor) { 155 | LogReader.call(this, { 156 | 'shared-library': { parsers: [null, parseInt, parseInt], 157 | processor: this.processSharedLibrary }, 158 | 'code-creation': { 159 | parsers: [null, parseInt, parseInt, null, 'var-args'], 160 | processor: this.processCodeCreation }, 161 | 'code-move': { parsers: [parseInt, parseInt], 162 | processor: this.processCodeMove }, 163 | 'code-delete': { parsers: [parseInt], 164 | processor: this.processCodeDelete }, 165 | 'sfi-move': { parsers: [parseInt, parseInt], 166 | processor: this.processFunctionMove }, 167 | 'snapshot-pos': { parsers: [parseInt, parseInt], 168 | processor: this.processSnapshotPosition }, 169 | 'tick': { 170 | parsers: [parseInt, parseInt, parseInt, 171 | parseInt, parseInt, 'var-args'], 172 | processor: this.processTick }, 173 | 'heap-sample-begin': { parsers: [null, null, parseInt], 174 | processor: this.processHeapSampleBegin }, 175 | 'heap-sample-end': { parsers: [null, null], 176 | processor: this.processHeapSampleEnd }, 177 | // Ignored events. 178 | 'profiler': null, 179 | 'function-creation': null, 180 | 'function-move': null, 181 | 'function-delete': null, 182 | 'heap-sample-item': null, 183 | // Obsolete row types. 184 | 'code-allocate': null, 185 | 'begin-code-region': null, 186 | 'end-code-region': null }); 187 | 188 | this.cppEntriesProvider_ = cppEntriesProvider; 189 | this.callGraphSize_ = callGraphSize; 190 | this.ignoreUnknown_ = ignoreUnknown; 191 | this.stateFilter_ = stateFilter; 192 | this.snapshotLogProcessor_ = snapshotLogProcessor; 193 | this.deserializedEntriesNames_ = []; 194 | var ticks = this.ticks_ = 195 | { total: 0, unaccounted: 0, excluded: 0, gc: 0 }; 196 | 197 | V8Profile.prototype.handleUnknownCode = function( 198 | operation, addr, opt_stackPos) { 199 | var op = Profile.Operation; 200 | switch (operation) { 201 | case op.MOVE: 202 | print('Code move event for unknown code: 0x' + addr.toString(16)); 203 | break; 204 | case op.DELETE: 205 | print('Code delete event for unknown code: 0x' + addr.toString(16)); 206 | break; 207 | case op.TICK: 208 | // Only unknown PCs (the first frame) are reported as unaccounted, 209 | // otherwise tick balance will be corrupted (this behavior is compatible 210 | // with the original tickprocessor.py script.) 211 | if (opt_stackPos == 0) { 212 | ticks.unaccounted++; 213 | } 214 | break; 215 | } 216 | }; 217 | 218 | this.profile_ = new V8Profile(separateIc); 219 | this.codeTypes_ = {}; 220 | // Count each tick as a time unit. 221 | this.viewBuilder_ = new ViewBuilder(1); 222 | this.lastLogFileName_ = null; 223 | 224 | this.generation_ = 1; 225 | this.currentProducerProfile_ = null; 226 | }; 227 | inherits(TickProcessor, LogReader); 228 | 229 | 230 | TickProcessor.VmStates = { 231 | JS: 0, 232 | GC: 1, 233 | COMPILER: 2, 234 | OTHER: 3, 235 | EXTERNAL: 4 236 | }; 237 | 238 | 239 | TickProcessor.CodeTypes = { 240 | CPP: 0, 241 | SHARED_LIB: 1 242 | }; 243 | // Otherwise, this is JS-related code. We are not adding it to 244 | // codeTypes_ map because there can be zillions of them. 245 | 246 | 247 | TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0; 248 | 249 | TickProcessor.CALL_GRAPH_SIZE = 5; 250 | 251 | /** 252 | * @override 253 | */ 254 | TickProcessor.prototype.printError = function(str) { 255 | print(str); 256 | }; 257 | 258 | 259 | TickProcessor.prototype.setCodeType = function(name, type) { 260 | this.codeTypes_[name] = TickProcessor.CodeTypes[type]; 261 | }; 262 | 263 | 264 | TickProcessor.prototype.isSharedLibrary = function(name) { 265 | return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB; 266 | }; 267 | 268 | 269 | TickProcessor.prototype.isCppCode = function(name) { 270 | return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP; 271 | }; 272 | 273 | 274 | TickProcessor.prototype.isJsCode = function(name) { 275 | return !(name in this.codeTypes_); 276 | }; 277 | 278 | 279 | TickProcessor.prototype.processLogFile = function(fileName) { 280 | this.lastLogFileName_ = fileName; 281 | var line; 282 | while (line = readline()) { 283 | this.processLogLine(line); 284 | } 285 | }; 286 | 287 | 288 | TickProcessor.prototype.processLogFileInTest = function(fileName) { 289 | // Hack file name to avoid dealing with platform specifics. 290 | this.lastLogFileName_ = 'v8.log'; 291 | var contents = readFile(fileName); 292 | this.processLogChunk(contents); 293 | }; 294 | 295 | 296 | TickProcessor.prototype.processSharedLibrary = function( 297 | name, startAddr, endAddr) { 298 | var entry = this.profile_.addLibrary(name, startAddr, endAddr); 299 | this.setCodeType(entry.getName(), 'SHARED_LIB'); 300 | 301 | var self = this; 302 | var libFuncs = this.cppEntriesProvider_.parseVmSymbols( 303 | name, startAddr, endAddr, function(fName, fStart, fEnd) { 304 | self.profile_.addStaticCode(fName, fStart, fEnd); 305 | self.setCodeType(fName, 'CPP'); 306 | }); 307 | }; 308 | 309 | 310 | TickProcessor.prototype.processCodeCreation = function( 311 | type, start, size, name, maybe_func) { 312 | name = this.deserializedEntriesNames_[start] || name; 313 | if (maybe_func.length) { 314 | var funcAddr = parseInt(maybe_func[0]); 315 | var state = parseState(maybe_func[1]); 316 | this.profile_.addFuncCode(type, name, start, size, funcAddr, state); 317 | } else { 318 | this.profile_.addCode(type, name, start, size); 319 | } 320 | }; 321 | 322 | 323 | TickProcessor.prototype.processCodeMove = function(from, to) { 324 | this.profile_.moveCode(from, to); 325 | }; 326 | 327 | 328 | TickProcessor.prototype.processCodeDelete = function(start) { 329 | this.profile_.deleteCode(start); 330 | }; 331 | 332 | 333 | TickProcessor.prototype.processFunctionMove = function(from, to) { 334 | this.profile_.moveFunc(from, to); 335 | }; 336 | 337 | 338 | TickProcessor.prototype.processSnapshotPosition = function(addr, pos) { 339 | if (this.snapshotLogProcessor_) { 340 | this.deserializedEntriesNames_[addr] = 341 | this.snapshotLogProcessor_.getSerializedEntryName(pos); 342 | } 343 | }; 344 | 345 | 346 | TickProcessor.prototype.includeTick = function(vmState) { 347 | return this.stateFilter_ == null || this.stateFilter_ == vmState; 348 | }; 349 | 350 | TickProcessor.prototype.processTick = function(pc, 351 | sp, 352 | is_external_callback, 353 | tos_or_external_callback, 354 | vmState, 355 | stack) { 356 | this.ticks_.total++; 357 | if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++; 358 | if (!this.includeTick(vmState)) { 359 | this.ticks_.excluded++; 360 | return; 361 | } 362 | if (is_external_callback) { 363 | // Don't use PC when in external callback code, as it can point 364 | // inside callback's code, and we will erroneously report 365 | // that a callback calls itself. Instead we use tos_or_external_callback, 366 | // as simply resetting PC will produce unaccounted ticks. 367 | pc = tos_or_external_callback; 368 | tos_or_external_callback = 0; 369 | } else if (tos_or_external_callback) { 370 | // Find out, if top of stack was pointing inside a JS function 371 | // meaning that we have encountered a frameless invocation. 372 | var funcEntry = this.profile_.findEntry(tos_or_external_callback); 373 | if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) { 374 | tos_or_external_callback = 0; 375 | } 376 | } 377 | 378 | this.profile_.recordTick(this.processStack(pc, tos_or_external_callback, stack)); 379 | }; 380 | 381 | 382 | TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) { 383 | if (space != 'Heap') return; 384 | this.currentProducerProfile_ = new CallTree(); 385 | }; 386 | 387 | 388 | TickProcessor.prototype.processHeapSampleEnd = function(space, state) { 389 | if (space != 'Heap' || !this.currentProducerProfile_) return; 390 | 391 | print('Generation ' + this.generation_ + ':'); 392 | var tree = this.currentProducerProfile_; 393 | tree.computeTotalWeights(); 394 | var producersView = this.viewBuilder_.buildView(tree); 395 | // Sort by total time, desc, then by name, desc. 396 | producersView.sort(function(rec1, rec2) { 397 | return rec2.totalTime - rec1.totalTime || 398 | (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 399 | this.printHeavyProfile(producersView.head.children); 400 | 401 | this.currentProducerProfile_ = null; 402 | this.generation_++; 403 | }; 404 | 405 | 406 | TickProcessor.prototype.printStatistics = function() { 407 | print('Statistical profiling result from ' + this.lastLogFileName_ + 408 | ', (' + this.ticks_.total + 409 | ' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' + 410 | this.ticks_.excluded + ' excluded).'); 411 | 412 | if (this.ticks_.total == 0) return; 413 | 414 | // Print the unknown ticks percentage if they are not ignored. 415 | if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) { 416 | this.printHeader('Unknown'); 417 | this.printCounter(this.ticks_.unaccounted, this.ticks_.total); 418 | } 419 | 420 | var flatProfile = this.profile_.getFlatProfile(); 421 | var flatView = this.viewBuilder_.buildView(flatProfile); 422 | // Sort by self time, desc, then by name, desc. 423 | flatView.sort(function(rec1, rec2) { 424 | return rec2.selfTime - rec1.selfTime || 425 | (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 426 | var totalTicks = this.ticks_.total; 427 | if (this.ignoreUnknown_) { 428 | totalTicks -= this.ticks_.unaccounted; 429 | } 430 | // Our total time contains all the ticks encountered, 431 | // while profile only knows about the filtered ticks. 432 | flatView.head.totalTime = totalTicks; 433 | 434 | // Count library ticks 435 | var flatViewNodes = flatView.head.children; 436 | var self = this; 437 | var libraryTicks = 0; 438 | this.processProfile(flatViewNodes, 439 | function(name) { return self.isSharedLibrary(name); }, 440 | function(rec) { libraryTicks += rec.selfTime; }); 441 | var nonLibraryTicks = totalTicks - libraryTicks; 442 | 443 | this.printHeader('Shared libraries'); 444 | this.printEntries(flatViewNodes, null, 445 | function(name) { return self.isSharedLibrary(name); }); 446 | 447 | this.printHeader('JavaScript'); 448 | this.printEntries(flatViewNodes, nonLibraryTicks, 449 | function(name) { return self.isJsCode(name); }); 450 | 451 | this.printHeader('C++'); 452 | this.printEntries(flatViewNodes, nonLibraryTicks, 453 | function(name) { return self.isCppCode(name); }); 454 | 455 | this.printHeader('GC'); 456 | this.printCounter(this.ticks_.gc, totalTicks); 457 | 458 | this.printHeavyProfHeader(); 459 | var heavyProfile = this.profile_.getBottomUpProfile(); 460 | var heavyView = this.viewBuilder_.buildView(heavyProfile); 461 | // To show the same percentages as in the flat profile. 462 | heavyView.head.totalTime = totalTicks; 463 | // Sort by total time, desc, then by name, desc. 464 | heavyView.sort(function(rec1, rec2) { 465 | return rec2.totalTime - rec1.totalTime || 466 | (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 467 | this.printHeavyProfile(heavyView.head.children); 468 | }; 469 | 470 | 471 | function padLeft(s, len) { 472 | s = s.toString(); 473 | if (s.length < len) { 474 | var padLength = len - s.length; 475 | if (!(padLength in padLeft)) { 476 | padLeft[padLength] = new Array(padLength + 1).join(' '); 477 | } 478 | s = padLeft[padLength] + s; 479 | } 480 | return s; 481 | }; 482 | 483 | 484 | TickProcessor.prototype.printHeader = function(headerTitle) { 485 | print('\n [' + headerTitle + ']:'); 486 | print(' ticks total nonlib name'); 487 | }; 488 | 489 | 490 | TickProcessor.prototype.printHeavyProfHeader = function() { 491 | print('\n [Bottom up (heavy) profile]:'); 492 | print(' Note: percentage shows a share of a particular caller in the ' + 493 | 'total\n' + 494 | ' amount of its parent calls.'); 495 | print(' Callers occupying less than ' + 496 | TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) + 497 | '% are not shown.\n'); 498 | print(' ticks parent name'); 499 | }; 500 | 501 | 502 | TickProcessor.prototype.printCounter = function(ticksCount, totalTicksCount) { 503 | var pct = ticksCount * 100.0 / totalTicksCount; 504 | print(' ' + padLeft(ticksCount, 5) + ' ' + padLeft(pct.toFixed(1), 5) + '%'); 505 | }; 506 | 507 | 508 | TickProcessor.prototype.processProfile = function( 509 | profile, filterP, func) { 510 | for (var i = 0, n = profile.length; i < n; ++i) { 511 | var rec = profile[i]; 512 | if (!filterP(rec.internalFuncName)) { 513 | continue; 514 | } 515 | func(rec); 516 | } 517 | }; 518 | 519 | 520 | TickProcessor.prototype.printEntries = function( 521 | profile, nonLibTicks, filterP) { 522 | this.processProfile(profile, filterP, function (rec) { 523 | if (rec.selfTime == 0) return; 524 | var nonLibPct = nonLibTicks != null ? 525 | rec.selfTime * 100.0 / nonLibTicks : 0.0; 526 | print(' ' + padLeft(rec.selfTime, 5) + ' ' + 527 | padLeft(rec.selfPercent.toFixed(1), 5) + '% ' + 528 | padLeft(nonLibPct.toFixed(1), 5) + '% ' + 529 | rec.internalFuncName); 530 | }); 531 | }; 532 | 533 | 534 | TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) { 535 | var self = this; 536 | var indent = opt_indent || 0; 537 | var indentStr = padLeft('', indent); 538 | this.processProfile(profile, function() { return true; }, function (rec) { 539 | // Cut off too infrequent callers. 540 | if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return; 541 | print(' ' + padLeft(rec.totalTime, 5) + ' ' + 542 | padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' + 543 | indentStr + rec.internalFuncName); 544 | // Limit backtrace depth. 545 | if (indent < 2 * self.callGraphSize_) { 546 | self.printHeavyProfile(rec.children, indent + 2); 547 | } 548 | // Delimit top-level functions. 549 | if (indent == 0) { 550 | print(''); 551 | } 552 | }); 553 | }; 554 | 555 | 556 | function CppEntriesProvider() { 557 | }; 558 | 559 | 560 | CppEntriesProvider.prototype.parseVmSymbols = function( 561 | libName, libStart, libEnd, processorFunc) { 562 | this.loadSymbols(libName); 563 | 564 | var prevEntry; 565 | 566 | function addEntry(funcInfo) { 567 | // Several functions can be mapped onto the same address. To avoid 568 | // creating zero-sized entries, skip such duplicates. 569 | // Also double-check that function belongs to the library address space. 570 | if (prevEntry && !prevEntry.end && 571 | prevEntry.start < funcInfo.start && 572 | prevEntry.start >= libStart && funcInfo.start <= libEnd) { 573 | processorFunc(prevEntry.name, prevEntry.start, funcInfo.start); 574 | } 575 | if (funcInfo.end && 576 | (!prevEntry || prevEntry.start != funcInfo.start) && 577 | funcInfo.start >= libStart && funcInfo.end <= libEnd) { 578 | processorFunc(funcInfo.name, funcInfo.start, funcInfo.end); 579 | } 580 | prevEntry = funcInfo; 581 | } 582 | 583 | while (true) { 584 | var funcInfo = this.parseNextLine(); 585 | if (funcInfo === null) { 586 | continue; 587 | } else if (funcInfo === false) { 588 | break; 589 | } 590 | if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) { 591 | funcInfo.start += libStart; 592 | } 593 | if (funcInfo.size) { 594 | funcInfo.end = funcInfo.start + funcInfo.size; 595 | } 596 | addEntry(funcInfo); 597 | } 598 | addEntry({name: '', start: libEnd}); 599 | }; 600 | 601 | 602 | CppEntriesProvider.prototype.loadSymbols = function(libName) { 603 | }; 604 | 605 | 606 | CppEntriesProvider.prototype.parseNextLine = function() { 607 | return false; 608 | }; 609 | 610 | 611 | function UnixCppEntriesProvider(nmExec, targetRootFS) { 612 | this.symbols = []; 613 | this.parsePos = 0; 614 | this.nmExec = nmExec; 615 | this.targetRootFS = targetRootFS; 616 | this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/; 617 | }; 618 | inherits(UnixCppEntriesProvider, CppEntriesProvider); 619 | 620 | 621 | UnixCppEntriesProvider.prototype.loadSymbols = function(libName) { 622 | this.parsePos = 0; 623 | libName = this.targetRootFS + libName; 624 | try { 625 | this.symbols = [ 626 | os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1), 627 | os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1) 628 | ]; 629 | } catch (e) { 630 | // If the library cannot be found on this system let's not panic. 631 | this.symbols = ['', '']; 632 | } 633 | }; 634 | 635 | 636 | UnixCppEntriesProvider.prototype.parseNextLine = function() { 637 | if (this.symbols.length == 0) { 638 | return false; 639 | } 640 | var lineEndPos = this.symbols[0].indexOf('\n', this.parsePos); 641 | if (lineEndPos == -1) { 642 | this.symbols.shift(); 643 | this.parsePos = 0; 644 | return this.parseNextLine(); 645 | } 646 | 647 | var line = this.symbols[0].substring(this.parsePos, lineEndPos); 648 | this.parsePos = lineEndPos + 1; 649 | var fields = line.match(this.FUNC_RE); 650 | var funcInfo = null; 651 | if (fields) { 652 | funcInfo = { name: fields[3], start: parseInt(fields[1], 16) }; 653 | if (fields[2]) { 654 | funcInfo.size = parseInt(fields[2], 16); 655 | } 656 | } 657 | return funcInfo; 658 | }; 659 | 660 | 661 | function MacCppEntriesProvider(nmExec, targetRootFS) { 662 | UnixCppEntriesProvider.call(this, nmExec, targetRootFS); 663 | // Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups. 664 | this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ()[iItT] (.*)$/; 665 | }; 666 | inherits(MacCppEntriesProvider, UnixCppEntriesProvider); 667 | 668 | 669 | MacCppEntriesProvider.prototype.loadSymbols = function(libName) { 670 | this.parsePos = 0; 671 | libName = this.targetRootFS + libName; 672 | try { 673 | this.symbols = [os.system(this.nmExec, ['-n', '-f', libName], -1, -1), '']; 674 | } catch (e) { 675 | // If the library cannot be found on this system let's not panic. 676 | this.symbols = ''; 677 | } 678 | }; 679 | 680 | 681 | function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS) { 682 | this.targetRootFS = targetRootFS; 683 | this.symbols = ''; 684 | this.parsePos = 0; 685 | }; 686 | inherits(WindowsCppEntriesProvider, CppEntriesProvider); 687 | 688 | 689 | WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/; 690 | 691 | 692 | WindowsCppEntriesProvider.FUNC_RE = 693 | /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/; 694 | 695 | 696 | WindowsCppEntriesProvider.IMAGE_BASE_RE = 697 | /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/; 698 | 699 | 700 | // This is almost a constant on Windows. 701 | WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000; 702 | 703 | 704 | WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) { 705 | libName = this.targetRootFS + libName; 706 | var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE); 707 | if (!fileNameFields) return; 708 | var mapFileName = fileNameFields[1] + '.map'; 709 | this.moduleType_ = fileNameFields[2].toLowerCase(); 710 | try { 711 | this.symbols = read(mapFileName); 712 | } catch (e) { 713 | // If .map file cannot be found let's not panic. 714 | this.symbols = ''; 715 | } 716 | }; 717 | 718 | 719 | WindowsCppEntriesProvider.prototype.parseNextLine = function() { 720 | var lineEndPos = this.symbols.indexOf('\r\n', this.parsePos); 721 | if (lineEndPos == -1) { 722 | return false; 723 | } 724 | 725 | var line = this.symbols.substring(this.parsePos, lineEndPos); 726 | this.parsePos = lineEndPos + 2; 727 | 728 | // Image base entry is above all other symbols, so we can just 729 | // terminate parsing. 730 | var imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE); 731 | if (imageBaseFields) { 732 | var imageBase = parseInt(imageBaseFields[1], 16); 733 | if ((this.moduleType_ == 'exe') != 734 | (imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) { 735 | return false; 736 | } 737 | } 738 | 739 | var fields = line.match(WindowsCppEntriesProvider.FUNC_RE); 740 | return fields ? 741 | { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } : 742 | null; 743 | }; 744 | 745 | 746 | /** 747 | * Performs very simple unmangling of C++ names. 748 | * 749 | * Does not handle arguments and template arguments. The mangled names have 750 | * the form: 751 | * 752 | * ?LookupInDescriptor@JSObject@internal@v8@@...arguments info... 753 | */ 754 | WindowsCppEntriesProvider.prototype.unmangleName = function(name) { 755 | // Empty or non-mangled name. 756 | if (name.length < 1 || name.charAt(0) != '?') return name; 757 | var nameEndPos = name.indexOf('@@'); 758 | var components = name.substring(1, nameEndPos).split('@'); 759 | components.reverse(); 760 | return components.join('::'); 761 | }; 762 | 763 | 764 | function ArgumentsProcessor(args) { 765 | this.args_ = args; 766 | this.result_ = ArgumentsProcessor.DEFAULTS; 767 | 768 | this.argsDispatch_ = { 769 | '-j': ['stateFilter', TickProcessor.VmStates.JS, 770 | 'Show only ticks from JS VM state'], 771 | '-g': ['stateFilter', TickProcessor.VmStates.GC, 772 | 'Show only ticks from GC VM state'], 773 | '-c': ['stateFilter', TickProcessor.VmStates.COMPILER, 774 | 'Show only ticks from COMPILER VM state'], 775 | '-o': ['stateFilter', TickProcessor.VmStates.OTHER, 776 | 'Show only ticks from OTHER VM state'], 777 | '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL, 778 | 'Show only ticks from EXTERNAL VM state'], 779 | '--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE, 780 | 'Set the call graph size'], 781 | '--ignore-unknown': ['ignoreUnknown', true, 782 | 'Exclude ticks of unknown code entries from processing'], 783 | '--separate-ic': ['separateIc', true, 784 | 'Separate IC entries'], 785 | '--unix': ['platform', 'unix', 786 | 'Specify that we are running on *nix platform'], 787 | '--windows': ['platform', 'windows', 788 | 'Specify that we are running on Windows platform'], 789 | '--mac': ['platform', 'mac', 790 | 'Specify that we are running on Mac OS X platform'], 791 | '--nm': ['nm', 'nm', 792 | 'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'], 793 | '--target': ['targetRootFS', '', 794 | 'Specify the target root directory for cross environment'], 795 | '--snapshot-log': ['snapshotLogFileName', 'snapshot.log', 796 | 'Specify snapshot log file to use (e.g. --snapshot-log=snapshot.log)'] 797 | }; 798 | this.argsDispatch_['--js'] = this.argsDispatch_['-j']; 799 | this.argsDispatch_['--gc'] = this.argsDispatch_['-g']; 800 | this.argsDispatch_['--compiler'] = this.argsDispatch_['-c']; 801 | this.argsDispatch_['--other'] = this.argsDispatch_['-o']; 802 | this.argsDispatch_['--external'] = this.argsDispatch_['-e']; 803 | }; 804 | 805 | 806 | ArgumentsProcessor.DEFAULTS = { 807 | logFileName: 'v8.log', 808 | snapshotLogFileName: null, 809 | platform: 'unix', 810 | stateFilter: null, 811 | callGraphSize: 5, 812 | ignoreUnknown: false, 813 | separateIc: false, 814 | targetRootFS: '', 815 | nm: 'nm' 816 | }; 817 | 818 | 819 | ArgumentsProcessor.prototype.parse = function() { 820 | while (this.args_.length) { 821 | var arg = this.args_[0]; 822 | if (arg.charAt(0) != '-') { 823 | break; 824 | } 825 | this.args_.shift(); 826 | var userValue = null; 827 | var eqPos = arg.indexOf('='); 828 | if (eqPos != -1) { 829 | userValue = arg.substr(eqPos + 1); 830 | arg = arg.substr(0, eqPos); 831 | } 832 | if (arg in this.argsDispatch_) { 833 | var dispatch = this.argsDispatch_[arg]; 834 | this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue; 835 | } else { 836 | return false; 837 | } 838 | } 839 | 840 | if (this.args_.length >= 1) { 841 | this.result_.logFileName = this.args_.shift(); 842 | } 843 | return true; 844 | }; 845 | 846 | 847 | ArgumentsProcessor.prototype.result = function() { 848 | return this.result_; 849 | }; 850 | 851 | 852 | ArgumentsProcessor.prototype.printUsageAndExit = function() { 853 | 854 | function padRight(s, len) { 855 | s = s.toString(); 856 | if (s.length < len) { 857 | s = s + (new Array(len - s.length + 1).join(' ')); 858 | } 859 | return s; 860 | } 861 | 862 | print('Cmdline args: [options] [log-file-name]\n' + 863 | 'Default log file name is "' + 864 | ArgumentsProcessor.DEFAULTS.logFileName + '".\n'); 865 | print('Options:'); 866 | for (var arg in this.argsDispatch_) { 867 | var synonims = [arg]; 868 | var dispatch = this.argsDispatch_[arg]; 869 | for (var synArg in this.argsDispatch_) { 870 | if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) { 871 | synonims.push(synArg); 872 | delete this.argsDispatch_[synArg]; 873 | } 874 | } 875 | print(' ' + padRight(synonims.join(', '), 20) + dispatch[2]); 876 | } 877 | quit(2); 878 | }; 879 | 880 | --------------------------------------------------------------------------------