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, 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, kind, 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 distortion, 156 range) { 157 LogReader.call(this, { 158 'shared-library': { parsers: [null, parseInt, parseInt], 159 processor: this.processSharedLibrary }, 160 'code-creation': { 161 parsers: [null, parseInt, parseInt, parseInt, null, 'var-args'], 162 processor: this.processCodeCreation }, 163 'code-move': { parsers: [parseInt, parseInt], 164 processor: this.processCodeMove }, 165 'code-delete': { parsers: [parseInt], 166 processor: this.processCodeDelete }, 167 'sfi-move': { parsers: [parseInt, parseInt], 168 processor: this.processFunctionMove }, 169 'snapshot-pos': { parsers: [parseInt, parseInt], 170 processor: this.processSnapshotPosition }, 171 'tick': { 172 parsers: [parseInt, parseInt, parseInt, 173 parseInt, parseInt, 'var-args'], 174 processor: this.processTick }, 175 'heap-sample-begin': { parsers: [null, null, parseInt], 176 processor: this.processHeapSampleBegin }, 177 'heap-sample-end': { parsers: [null, null], 178 processor: this.processHeapSampleEnd }, 179 'timer-event-start' : { parsers: [null, null, null], 180 processor: this.advanceDistortion }, 181 'timer-event-end' : { parsers: [null, null, null], 182 processor: this.advanceDistortion }, 183 // Ignored events. 184 'profiler': null, 185 'function-creation': null, 186 'function-move': null, 187 'function-delete': null, 188 'heap-sample-item': null, 189 // Obsolete row types. 190 'code-allocate': null, 191 'begin-code-region': null, 192 'end-code-region': null }); 193 194 this.cppEntriesProvider_ = cppEntriesProvider; 195 this.callGraphSize_ = callGraphSize; 196 this.ignoreUnknown_ = ignoreUnknown; 197 this.stateFilter_ = stateFilter; 198 this.snapshotLogProcessor_ = snapshotLogProcessor; 199 this.deserializedEntriesNames_ = []; 200 var ticks = this.ticks_ = 201 { total: 0, unaccounted: 0, excluded: 0, gc: 0 }; 202 203 distortion = parseInt(distortion); 204 // Convert picoseconds to nanoseconds. 205 this.distortion_per_entry = isNaN(distortion) ? 0 : (distortion / 1000); 206 this.distortion = 0; 207 var rangelimits = range ? range.split(",") : []; 208 var range_start = parseInt(rangelimits[0]); 209 var range_end = parseInt(rangelimits[1]); 210 // Convert milliseconds to nanoseconds. 211 this.range_start = isNaN(range_start) ? -Infinity : (range_start * 1000); 212 this.range_end = isNaN(range_end) ? Infinity : (range_end * 1000) 213 214 V8Profile.prototype.handleUnknownCode = function( 215 operation, addr, opt_stackPos) { 216 var op = Profile.Operation; 217 switch (operation) { 218 case op.MOVE: 219 print('Code move event for unknown code: 0x' + addr.toString(16)); 220 break; 221 case op.DELETE: 222 print('Code delete event for unknown code: 0x' + addr.toString(16)); 223 break; 224 case op.TICK: 225 // Only unknown PCs (the first frame) are reported as unaccounted, 226 // otherwise tick balance will be corrupted (this behavior is compatible 227 // with the original tickprocessor.py script.) 228 if (opt_stackPos == 0) { 229 ticks.unaccounted++; 230 } 231 break; 232 } 233 }; 234 235 this.profile_ = new V8Profile(separateIc); 236 this.codeTypes_ = {}; 237 // Count each tick as a time unit. 238 this.viewBuilder_ = new ViewBuilder(1); 239 this.lastLogFileName_ = null; 240 241 this.generation_ = 1; 242 this.currentProducerProfile_ = null; 243 }; 244 inherits(TickProcessor, LogReader); 245 246 247 TickProcessor.VmStates = { 248 JS: 0, 249 GC: 1, 250 COMPILER: 2, 251 OTHER: 3, 252 EXTERNAL: 4, 253 IDLE: 5 254 }; 255 256 257 TickProcessor.CodeTypes = { 258 CPP: 0, 259 SHARED_LIB: 1 260 }; 261 // Otherwise, this is JS-related code. We are not adding it to 262 // codeTypes_ map because there can be zillions of them. 263 264 265 TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0; 266 267 TickProcessor.CALL_GRAPH_SIZE = 5; 268 269 /** 270 * @override 271 */ 272 TickProcessor.prototype.printError = function(str) { 273 print(str); 274 }; 275 276 277 TickProcessor.prototype.setCodeType = function(name, type) { 278 this.codeTypes_[name] = TickProcessor.CodeTypes[type]; 279 }; 280 281 282 TickProcessor.prototype.isSharedLibrary = function(name) { 283 return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB; 284 }; 285 286 287 TickProcessor.prototype.isCppCode = function(name) { 288 return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP; 289 }; 290 291 292 TickProcessor.prototype.isJsCode = function(name) { 293 return !(name in this.codeTypes_); 294 }; 295 296 297 TickProcessor.prototype.processLogFile = function(fileName) { 298 this.lastLogFileName_ = fileName; 299 var line; 300 while (line = readline()) { 301 this.processLogLine(line); 302 } 303 }; 304 305 306 TickProcessor.prototype.processLogFileInTest = function(fileName) { 307 // Hack file name to avoid dealing with platform specifics. 308 this.lastLogFileName_ = 'v8.log'; 309 var contents = readFile(fileName); 310 this.processLogChunk(contents); 311 }; 312 313 314 TickProcessor.prototype.processSharedLibrary = function( 315 name, startAddr, endAddr) { 316 var entry = this.profile_.addLibrary(name, startAddr, endAddr); 317 this.setCodeType(entry.getName(), 'SHARED_LIB'); 318 319 var self = this; 320 var libFuncs = this.cppEntriesProvider_.parseVmSymbols( 321 name, startAddr, endAddr, function(fName, fStart, fEnd) { 322 self.profile_.addStaticCode(fName, fStart, fEnd); 323 self.setCodeType(fName, 'CPP'); 324 }); 325 }; 326 327 328 TickProcessor.prototype.processCodeCreation = function( 329 type, kind, start, size, name, maybe_func) { 330 name = this.deserializedEntriesNames_[start] || name; 331 if (maybe_func.length) { 332 var funcAddr = parseInt(maybe_func[0]); 333 var state = parseState(maybe_func[1]); 334 this.profile_.addFuncCode(type, name, start, size, funcAddr, state); 335 } else { 336 this.profile_.addCode(type, name, start, size); 337 } 338 }; 339 340 341 TickProcessor.prototype.processCodeMove = function(from, to) { 342 this.profile_.moveCode(from, to); 343 }; 344 345 346 TickProcessor.prototype.processCodeDelete = function(start) { 347 this.profile_.deleteCode(start); 348 }; 349 350 351 TickProcessor.prototype.processFunctionMove = function(from, to) { 352 this.profile_.moveFunc(from, to); 353 }; 354 355 356 TickProcessor.prototype.processSnapshotPosition = function(addr, pos) { 357 if (this.snapshotLogProcessor_) { 358 this.deserializedEntriesNames_[addr] = 359 this.snapshotLogProcessor_.getSerializedEntryName(pos); 360 } 361 }; 362 363 364 TickProcessor.prototype.includeTick = function(vmState) { 365 return this.stateFilter_ == null || this.stateFilter_ == vmState; 366 }; 367 368 TickProcessor.prototype.processTick = function(pc, 369 ns_since_start, 370 is_external_callback, 371 tos_or_external_callback, 372 vmState, 373 stack) { 374 this.distortion += this.distortion_per_entry; 375 ns_since_start -= this.distortion; 376 if (ns_since_start < this.range_start || ns_since_start > this.range_end) { 377 return; 378 } 379 this.ticks_.total++; 380 if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++; 381 if (!this.includeTick(vmState)) { 382 this.ticks_.excluded++; 383 return; 384 } 385 if (is_external_callback) { 386 // Don't use PC when in external callback code, as it can point 387 // inside callback's code, and we will erroneously report 388 // that a callback calls itself. Instead we use tos_or_external_callback, 389 // as simply resetting PC will produce unaccounted ticks. 390 pc = tos_or_external_callback; 391 tos_or_external_callback = 0; 392 } else if (tos_or_external_callback) { 393 // Find out, if top of stack was pointing inside a JS function 394 // meaning that we have encountered a frameless invocation. 395 var funcEntry = this.profile_.findEntry(tos_or_external_callback); 396 if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) { 397 tos_or_external_callback = 0; 398 } 399 } 400 401 this.profile_.recordTick(this.processStack(pc, tos_or_external_callback, stack)); 402 }; 403 404 405 TickProcessor.prototype.advanceDistortion = function() { 406 this.distortion += this.distortion_per_entry; 407 } 408 409 410 TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) { 411 if (space != 'Heap') return; 412 this.currentProducerProfile_ = new CallTree(); 413 }; 414 415 416 TickProcessor.prototype.processHeapSampleEnd = function(space, state) { 417 if (space != 'Heap' || !this.currentProducerProfile_) return; 418 419 print('Generation ' + this.generation_ + ':'); 420 var tree = this.currentProducerProfile_; 421 tree.computeTotalWeights(); 422 var producersView = this.viewBuilder_.buildView(tree); 423 // Sort by total time, desc, then by name, desc. 424 producersView.sort(function(rec1, rec2) { 425 return rec2.totalTime - rec1.totalTime || 426 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 427 this.printHeavyProfile(producersView.head.children); 428 429 this.currentProducerProfile_ = null; 430 this.generation_++; 431 }; 432 433 434 TickProcessor.prototype.printStatistics = function() { 435 print('Statistical profiling result from ' + this.lastLogFileName_ + 436 ', (' + this.ticks_.total + 437 ' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' + 438 this.ticks_.excluded + ' excluded).'); 439 440 if (this.ticks_.total == 0) return; 441 442 // Print the unknown ticks percentage if they are not ignored. 443 if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) { 444 this.printHeader('Unknown'); 445 this.printCounter(this.ticks_.unaccounted, this.ticks_.total); 446 } 447 448 var flatProfile = this.profile_.getFlatProfile(); 449 var flatView = this.viewBuilder_.buildView(flatProfile); 450 // Sort by self time, desc, then by name, desc. 451 flatView.sort(function(rec1, rec2) { 452 return rec2.selfTime - rec1.selfTime || 453 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 454 var totalTicks = this.ticks_.total; 455 if (this.ignoreUnknown_) { 456 totalTicks -= this.ticks_.unaccounted; 457 } 458 // Our total time contains all the ticks encountered, 459 // while profile only knows about the filtered ticks. 460 flatView.head.totalTime = totalTicks; 461 462 // Count library ticks 463 var flatViewNodes = flatView.head.children; 464 var self = this; 465 var libraryTicks = 0; 466 this.processProfile(flatViewNodes, 467 function(name) { return self.isSharedLibrary(name); }, 468 function(rec) { libraryTicks += rec.selfTime; }); 469 var nonLibraryTicks = totalTicks - libraryTicks; 470 471 this.printHeader('Shared libraries'); 472 this.printEntries(flatViewNodes, null, 473 function(name) { return self.isSharedLibrary(name); }); 474 475 this.printHeader('JavaScript'); 476 this.printEntries(flatViewNodes, nonLibraryTicks, 477 function(name) { return self.isJsCode(name); }); 478 479 this.printHeader('C++'); 480 this.printEntries(flatViewNodes, nonLibraryTicks, 481 function(name) { return self.isCppCode(name); }); 482 483 this.printHeader('GC'); 484 this.printCounter(this.ticks_.gc, totalTicks); 485 486 this.printHeavyProfHeader(); 487 var heavyProfile = this.profile_.getBottomUpProfile(); 488 var heavyView = this.viewBuilder_.buildView(heavyProfile); 489 // To show the same percentages as in the flat profile. 490 heavyView.head.totalTime = totalTicks; 491 // Sort by total time, desc, then by name, desc. 492 heavyView.sort(function(rec1, rec2) { 493 return rec2.totalTime - rec1.totalTime || 494 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 495 this.printHeavyProfile(heavyView.head.children); 496 }; 497 498 499 function padLeft(s, len) { 500 s = s.toString(); 501 if (s.length < len) { 502 var padLength = len - s.length; 503 if (!(padLength in padLeft)) { 504 padLeft[padLength] = new Array(padLength + 1).join(' '); 505 } 506 s = padLeft[padLength] + s; 507 } 508 return s; 509 }; 510 511 512 TickProcessor.prototype.printHeader = function(headerTitle) { 513 print('\n [' + headerTitle + ']:'); 514 print(' ticks total nonlib name'); 515 }; 516 517 518 TickProcessor.prototype.printHeavyProfHeader = function() { 519 print('\n [Bottom up (heavy) profile]:'); 520 print(' Note: percentage shows a share of a particular caller in the ' + 521 'total\n' + 522 ' amount of its parent calls.'); 523 print(' Callers occupying less than ' + 524 TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) + 525 '% are not shown.\n'); 526 print(' ticks parent name'); 527 }; 528 529 530 TickProcessor.prototype.printCounter = function(ticksCount, totalTicksCount) { 531 var pct = ticksCount * 100.0 / totalTicksCount; 532 print(' ' + padLeft(ticksCount, 5) + ' ' + padLeft(pct.toFixed(1), 5) + '%'); 533 }; 534 535 536 TickProcessor.prototype.processProfile = function( 537 profile, filterP, func) { 538 for (var i = 0, n = profile.length; i < n; ++i) { 539 var rec = profile[i]; 540 if (!filterP(rec.internalFuncName)) { 541 continue; 542 } 543 func(rec); 544 } 545 }; 546 547 548 TickProcessor.prototype.printEntries = function( 549 profile, nonLibTicks, filterP) { 550 this.processProfile(profile, filterP, function (rec) { 551 if (rec.selfTime == 0) return; 552 var nonLibPct = nonLibTicks != null ? 553 rec.selfTime * 100.0 / nonLibTicks : 0.0; 554 print(' ' + padLeft(rec.selfTime, 5) + ' ' + 555 padLeft(rec.selfPercent.toFixed(1), 5) + '% ' + 556 padLeft(nonLibPct.toFixed(1), 5) + '% ' + 557 rec.internalFuncName); 558 }); 559 }; 560 561 562 TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) { 563 var self = this; 564 var indent = opt_indent || 0; 565 var indentStr = padLeft('', indent); 566 this.processProfile(profile, function() { return true; }, function (rec) { 567 // Cut off too infrequent callers. 568 if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return; 569 print(' ' + padLeft(rec.totalTime, 5) + ' ' + 570 padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' + 571 indentStr + rec.internalFuncName); 572 // Limit backtrace depth. 573 if (indent < 2 * self.callGraphSize_) { 574 self.printHeavyProfile(rec.children, indent + 2); 575 } 576 // Delimit top-level functions. 577 if (indent == 0) { 578 print(''); 579 } 580 }); 581 }; 582 583 584 function CppEntriesProvider() { 585 }; 586 587 588 CppEntriesProvider.prototype.parseVmSymbols = function( 589 libName, libStart, libEnd, processorFunc) { 590 this.loadSymbols(libName); 591 592 var prevEntry; 593 594 function addEntry(funcInfo) { 595 // Several functions can be mapped onto the same address. To avoid 596 // creating zero-sized entries, skip such duplicates. 597 // Also double-check that function belongs to the library address space. 598 if (prevEntry && !prevEntry.end && 599 prevEntry.start < funcInfo.start && 600 prevEntry.start >= libStart && funcInfo.start <= libEnd) { 601 processorFunc(prevEntry.name, prevEntry.start, funcInfo.start); 602 } 603 if (funcInfo.end && 604 (!prevEntry || prevEntry.start != funcInfo.start) && 605 funcInfo.start >= libStart && funcInfo.end <= libEnd) { 606 processorFunc(funcInfo.name, funcInfo.start, funcInfo.end); 607 } 608 prevEntry = funcInfo; 609 } 610 611 while (true) { 612 var funcInfo = this.parseNextLine(); 613 if (funcInfo === null) { 614 continue; 615 } else if (funcInfo === false) { 616 break; 617 } 618 if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) { 619 funcInfo.start += libStart; 620 } 621 if (funcInfo.size) { 622 funcInfo.end = funcInfo.start + funcInfo.size; 623 } 624 addEntry(funcInfo); 625 } 626 addEntry({name: '', start: libEnd}); 627 }; 628 629 630 CppEntriesProvider.prototype.loadSymbols = function(libName) { 631 }; 632 633 634 CppEntriesProvider.prototype.parseNextLine = function() { 635 return false; 636 }; 637 638 639 function UnixCppEntriesProvider(nmExec, targetRootFS) { 640 this.symbols = []; 641 this.parsePos = 0; 642 this.nmExec = nmExec; 643 this.targetRootFS = targetRootFS; 644 this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/; 645 }; 646 inherits(UnixCppEntriesProvider, CppEntriesProvider); 647 648 649 UnixCppEntriesProvider.prototype.loadSymbols = function(libName) { 650 this.parsePos = 0; 651 libName = this.targetRootFS + libName; 652 try { 653 this.symbols = [ 654 os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1), 655 os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1) 656 ]; 657 } catch (e) { 658 // If the library cannot be found on this system let's not panic. 659 this.symbols = ['', '']; 660 } 661 }; 662 663 664 UnixCppEntriesProvider.prototype.parseNextLine = function() { 665 if (this.symbols.length == 0) { 666 return false; 667 } 668 var lineEndPos = this.symbols[0].indexOf('\n', this.parsePos); 669 if (lineEndPos == -1) { 670 this.symbols.shift(); 671 this.parsePos = 0; 672 return this.parseNextLine(); 673 } 674 675 var line = this.symbols[0].substring(this.parsePos, lineEndPos); 676 this.parsePos = lineEndPos + 1; 677 var fields = line.match(this.FUNC_RE); 678 var funcInfo = null; 679 if (fields) { 680 funcInfo = { name: fields[3], start: parseInt(fields[1], 16) }; 681 if (fields[2]) { 682 funcInfo.size = parseInt(fields[2], 16); 683 } 684 } 685 return funcInfo; 686 }; 687 688 689 function MacCppEntriesProvider(nmExec, targetRootFS) { 690 UnixCppEntriesProvider.call(this, nmExec, targetRootFS); 691 // Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups. 692 this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ()[iItT] (.*)$/; 693 }; 694 inherits(MacCppEntriesProvider, UnixCppEntriesProvider); 695 696 697 MacCppEntriesProvider.prototype.loadSymbols = function(libName) { 698 this.parsePos = 0; 699 libName = this.targetRootFS + libName; 700 try { 701 this.symbols = [os.system(this.nmExec, ['-n', '-f', libName], -1, -1), '']; 702 } catch (e) { 703 // If the library cannot be found on this system let's not panic. 704 this.symbols = ''; 705 } 706 }; 707 708 709 function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS) { 710 this.targetRootFS = targetRootFS; 711 this.symbols = ''; 712 this.parsePos = 0; 713 }; 714 inherits(WindowsCppEntriesProvider, CppEntriesProvider); 715 716 717 WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/; 718 719 720 WindowsCppEntriesProvider.FUNC_RE = 721 /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/; 722 723 724 WindowsCppEntriesProvider.IMAGE_BASE_RE = 725 /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/; 726 727 728 // This is almost a constant on Windows. 729 WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000; 730 731 732 WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) { 733 libName = this.targetRootFS + libName; 734 var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE); 735 if (!fileNameFields) return; 736 var mapFileName = fileNameFields[1] + '.map'; 737 this.moduleType_ = fileNameFields[2].toLowerCase(); 738 try { 739 this.symbols = read(mapFileName); 740 } catch (e) { 741 // If .map file cannot be found let's not panic. 742 this.symbols = ''; 743 } 744 }; 745 746 747 WindowsCppEntriesProvider.prototype.parseNextLine = function() { 748 var lineEndPos = this.symbols.indexOf('\r\n', this.parsePos); 749 if (lineEndPos == -1) { 750 return false; 751 } 752 753 var line = this.symbols.substring(this.parsePos, lineEndPos); 754 this.parsePos = lineEndPos + 2; 755 756 // Image base entry is above all other symbols, so we can just 757 // terminate parsing. 758 var imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE); 759 if (imageBaseFields) { 760 var imageBase = parseInt(imageBaseFields[1], 16); 761 if ((this.moduleType_ == 'exe') != 762 (imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) { 763 return false; 764 } 765 } 766 767 var fields = line.match(WindowsCppEntriesProvider.FUNC_RE); 768 return fields ? 769 { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } : 770 null; 771 }; 772 773 774 /** 775 * Performs very simple unmangling of C++ names. 776 * 777 * Does not handle arguments and template arguments. The mangled names have 778 * the form: 779 * 780 * ?LookupInDescriptor@JSObject@internal@v8@@...arguments info... 781 */ 782 WindowsCppEntriesProvider.prototype.unmangleName = function(name) { 783 // Empty or non-mangled name. 784 if (name.length < 1 || name.charAt(0) != '?') return name; 785 var nameEndPos = name.indexOf('@@'); 786 var components = name.substring(1, nameEndPos).split('@'); 787 components.reverse(); 788 return components.join('::'); 789 }; 790 791 792 function ArgumentsProcessor(args) { 793 this.args_ = args; 794 this.result_ = ArgumentsProcessor.DEFAULTS; 795 796 this.argsDispatch_ = { 797 '-j': ['stateFilter', TickProcessor.VmStates.JS, 798 'Show only ticks from JS VM state'], 799 '-g': ['stateFilter', TickProcessor.VmStates.GC, 800 'Show only ticks from GC VM state'], 801 '-c': ['stateFilter', TickProcessor.VmStates.COMPILER, 802 'Show only ticks from COMPILER VM state'], 803 '-o': ['stateFilter', TickProcessor.VmStates.OTHER, 804 'Show only ticks from OTHER VM state'], 805 '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL, 806 'Show only ticks from EXTERNAL VM state'], 807 '--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE, 808 'Set the call graph size'], 809 '--ignore-unknown': ['ignoreUnknown', true, 810 'Exclude ticks of unknown code entries from processing'], 811 '--separate-ic': ['separateIc', true, 812 'Separate IC entries'], 813 '--unix': ['platform', 'unix', 814 'Specify that we are running on *nix platform'], 815 '--windows': ['platform', 'windows', 816 'Specify that we are running on Windows platform'], 817 '--mac': ['platform', 'mac', 818 'Specify that we are running on Mac OS X platform'], 819 '--nm': ['nm', 'nm', 820 'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'], 821 '--target': ['targetRootFS', '', 822 'Specify the target root directory for cross environment'], 823 '--snapshot-log': ['snapshotLogFileName', 'snapshot.log', 824 'Specify snapshot log file to use (e.g. --snapshot-log=snapshot.log)'], 825 '--range': ['range', 'auto,auto', 826 'Specify the range limit as [start],[end]'], 827 '--distortion': ['distortion', 0, 828 'Specify the logging overhead in picoseconds'] 829 }; 830 this.argsDispatch_['--js'] = this.argsDispatch_['-j']; 831 this.argsDispatch_['--gc'] = this.argsDispatch_['-g']; 832 this.argsDispatch_['--compiler'] = this.argsDispatch_['-c']; 833 this.argsDispatch_['--other'] = this.argsDispatch_['-o']; 834 this.argsDispatch_['--external'] = this.argsDispatch_['-e']; 835 }; 836 837 838 ArgumentsProcessor.DEFAULTS = { 839 logFileName: 'v8.log', 840 snapshotLogFileName: null, 841 platform: 'unix', 842 stateFilter: null, 843 callGraphSize: 5, 844 ignoreUnknown: false, 845 separateIc: false, 846 targetRootFS: '', 847 nm: 'nm', 848 range: 'auto,auto', 849 distortion: 0 850 }; 851 852 853 ArgumentsProcessor.prototype.parse = function() { 854 while (this.args_.length) { 855 var arg = this.args_[0]; 856 if (arg.charAt(0) != '-') { 857 break; 858 } 859 this.args_.shift(); 860 var userValue = null; 861 var eqPos = arg.indexOf('='); 862 if (eqPos != -1) { 863 userValue = arg.substr(eqPos + 1); 864 arg = arg.substr(0, eqPos); 865 } 866 if (arg in this.argsDispatch_) { 867 var dispatch = this.argsDispatch_[arg]; 868 this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue; 869 } else { 870 return false; 871 } 872 } 873 874 if (this.args_.length >= 1) { 875 this.result_.logFileName = this.args_.shift(); 876 } 877 return true; 878 }; 879 880 881 ArgumentsProcessor.prototype.result = function() { 882 return this.result_; 883 }; 884 885 886 ArgumentsProcessor.prototype.printUsageAndExit = function() { 887 888 function padRight(s, len) { 889 s = s.toString(); 890 if (s.length < len) { 891 s = s + (new Array(len - s.length + 1).join(' ')); 892 } 893 return s; 894 } 895 896 print('Cmdline args: [options] [log-file-name]\n' + 897 'Default log file name is "' + 898 ArgumentsProcessor.DEFAULTS.logFileName + '".\n'); 899 print('Options:'); 900 for (var arg in this.argsDispatch_) { 901 var synonims = [arg]; 902 var dispatch = this.argsDispatch_[arg]; 903 for (var synArg in this.argsDispatch_) { 904 if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) { 905 synonims.push(synArg); 906 delete this.argsDispatch_[synArg]; 907 } 908 } 909 print(' ' + padRight(synonims.join(', '), 20) + dispatch[2]); 910 } 911 quit(2); 912 }; 913 914