1// Copyright 2017 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// =========================================================================== 6class MapProcessor extends LogReader { 7 constructor() { 8 super(); 9 this.dispatchTable_ = { 10 'code-creation': { 11 parsers: [parseString, parseInt, parseInt, parseInt, parseInt, 12 parseString, parseVarArgs], 13 processor: this.processCodeCreation 14 }, 15 'code-move': { 16 parsers: [parseInt, parseInt], 17 'sfi-move': { 18 parsers: [parseInt, parseInt], 19 processor: this.processCodeMove 20 }, 21 'code-delete': { 22 parsers: [parseInt], 23 processor: this.processCodeDelete 24 }, 25 processor: this.processFunctionMove 26 }, 27 'map-create': { 28 parsers: [parseInt, parseInt, parseString], 29 processor: this.processMapCreate 30 }, 31 'map': { 32 parsers: [parseString, parseInt, parseInt, parseInt, parseInt, parseInt, 33 parseString, parseString, parseString 34 ], 35 processor: this.processMap 36 }, 37 'map-details': { 38 parsers: [parseInt, parseInt, parseString], 39 processor: this.processMapDetails 40 } 41 }; 42 this.profile_ = new Profile(); 43 this.timeline_ = new Timeline(); 44 } 45 46 printError(str) { 47 console.error(str); 48 throw str 49 } 50 51 processString(string) { 52 let end = string.length; 53 let current = 0; 54 let next = 0; 55 let line; 56 let i = 0; 57 let entry; 58 try { 59 while (current < end) { 60 next = string.indexOf("\n", current); 61 if (next === -1) break; 62 i++; 63 line = string.substring(current, next); 64 current = next + 1; 65 this.processLogLine(line); 66 } 67 } catch(e) { 68 console.error("Error occurred during parsing, trying to continue: " + e); 69 } 70 return this.finalize(); 71 } 72 73 processLogFile(fileName) { 74 this.collectEntries = true 75 this.lastLogFileName_ = fileName; 76 let line; 77 while (line = readline()) { 78 this.processLogLine(line); 79 } 80 return this.finalize(); 81 } 82 83 finalize() { 84 // TODO(cbruni): print stats; 85 this.timeline_.finalize(); 86 return this.timeline_; 87 } 88 89 addEntry(entry) { 90 this.entries.push(entry); 91 } 92 93 /** 94 * Parser for dynamic code optimization state. 95 */ 96 parseState(s) { 97 switch (s) { 98 case "": 99 return Profile.CodeState.COMPILED; 100 case "~": 101 return Profile.CodeState.OPTIMIZABLE; 102 case "*": 103 return Profile.CodeState.OPTIMIZED; 104 } 105 throw new Error("unknown code state: " + s); 106 } 107 108 processCodeCreation( 109 type, kind, timestamp, start, size, name, maybe_func) { 110 if (maybe_func.length) { 111 let funcAddr = parseInt(maybe_func[0]); 112 let state = this.parseState(maybe_func[1]); 113 this.profile_.addFuncCode(type, name, timestamp, start, size, funcAddr, state); 114 } else { 115 this.profile_.addCode(type, name, timestamp, start, size); 116 } 117 } 118 119 processCodeMove(from, to) { 120 this.profile_.moveCode(from, to); 121 } 122 123 processCodeDelete(start) { 124 this.profile_.deleteCode(start); 125 } 126 127 processFunctionMove(from, to) { 128 this.profile_.moveFunc(from, to); 129 } 130 131 formatPC(pc, line, column) { 132 let entry = this.profile_.findEntry(pc); 133 if (!entry) return "<unknown>" 134 if (entry.type == "Builtin") { 135 return entry.name; 136 } 137 let name = entry.func.getName(); 138 let re = /(.*):[0-9]+:[0-9]+$/; 139 let array = re.exec(name); 140 if (!array) { 141 entry = name; 142 } else { 143 entry = entry.getState() + array[1]; 144 } 145 return entry + ":" + line + ":" + column; 146 } 147 148 processMap(type, time, from, to, pc, line, column, reason, name) { 149 time = parseInt(time); 150 if (type == "Deprecate") return this.deprecateMap(type, time, from); 151 from = this.getExistingMap(from, time); 152 to = this.getExistingMap(to, time); 153 let edge = new Edge(type, name, reason, time, from, to); 154 edge.filePosition = this.formatPC(pc, line, column); 155 edge.finishSetup(); 156 } 157 158 deprecateMap(type, time, id) { 159 this.getExistingMap(id, time).deprecate(); 160 } 161 162 processMapCreate(time, id, string) { 163 // map-create events might override existing maps if the addresses get 164 // rcycled. Hence we do not check for existing maps. 165 let map = this.createMap(id, time); 166 map.description = string; 167 } 168 169 processMapDetails(time, id, string) { 170 //TODO(cbruni): fix initial map logging. 171 let map = this.getExistingMap(id, time); 172 if (!map.description) { 173 map.description = string; 174 } 175 } 176 177 createMap(id, time) { 178 let map = new V8Map(id, time); 179 this.timeline_.push(map); 180 return map; 181 } 182 183 getExistingMap(id, time) { 184 if (id === 0) return undefined; 185 let map = V8Map.get(id); 186 if (map === undefined) { 187 console.error("No map details provided: id=" + id); 188 // Manually patch in a map to continue running. 189 return this.createMap(id, time); 190 }; 191 return map; 192 } 193} 194 195// =========================================================================== 196 197class V8Map { 198 constructor(id, time = -1) { 199 if (!id) throw "Invalid ID"; 200 this.id = id; 201 this.time = time; 202 if (!(time > 0)) throw "Invalid time"; 203 this.description = ""; 204 this.edge = void 0; 205 this.children = []; 206 this.depth = 0; 207 this._isDeprecated = false; 208 this.deprecationTargets = null; 209 V8Map.set(id, this); 210 this.leftId = 0; 211 this.rightId = 0; 212 } 213 214 finalize(id) { 215 // Initialize preorder tree traversal Ids for fast subtree inclusion checks 216 if (id <= 0) throw "invalid id"; 217 let currentId = id; 218 this.leftId = currentId 219 this.children.forEach(edge => { 220 let map = edge.to; 221 currentId = map.finalize(currentId + 1); 222 }); 223 this.rightId = currentId + 1; 224 return currentId + 1; 225 } 226 227 parent() { 228 if (this.edge === void 0) return void 0; 229 return this.edge.from; 230 } 231 232 isDeprecated() { 233 return this._isDeprecated; 234 } 235 236 deprecate() { 237 this._isDeprecated = true; 238 } 239 240 isRoot() { 241 return this.edge == void 0 || this.edge.from == void 0; 242 } 243 244 contains(map) { 245 return this.leftId < map.leftId && map.rightId < this.rightId; 246 } 247 248 addEdge(edge) { 249 this.children.push(edge); 250 } 251 252 chunkIndex(chunks) { 253 // Did anybody say O(n)? 254 for (let i = 0; i < chunks.length; i++) { 255 let chunk = chunks[i]; 256 if (chunk.isEmpty()) continue; 257 if (chunk.last().time < this.time) continue; 258 return i; 259 } 260 return -1; 261 } 262 263 position(chunks) { 264 let index = this.chunkIndex(chunks); 265 let xFrom = (index + 0.5) * kChunkWidth; 266 let yFrom = kChunkHeight - chunks[index].yOffset(this); 267 return [xFrom, yFrom]; 268 } 269 270 transitions() { 271 let transitions = Object.create(null); 272 let current = this; 273 while (current) { 274 let edge = current.edge; 275 if (edge && edge.isTransition()) { 276 transitions[edge.name] = edge; 277 } 278 current = current.parent() 279 } 280 return transitions; 281 } 282 283 getType() { 284 return this.edge === void 0 ? "new" : this.edge.type; 285 } 286 287 getParents() { 288 let parents = []; 289 let current = this.parent(); 290 while (current) { 291 parents.push(current); 292 current = current.parent(); 293 } 294 return parents; 295 } 296 297 static get(id) { 298 if (!this.cache) return undefined; 299 return this.cache.get(id); 300 } 301 302 static set(id, map) { 303 if (!this.cache) this.cache = new Map(); 304 this.cache.set(id, map); 305 } 306} 307 308 309// =========================================================================== 310class Edge { 311 constructor(type, name, reason, time, from, to) { 312 this.type = type; 313 this.name = name; 314 this.reason = reason; 315 this.time = time; 316 this.from = from; 317 this.to = to; 318 this.filePosition = ""; 319 } 320 321 finishSetup() { 322 if (this.from) this.from.addEdge(this); 323 if (this.to) { 324 this.to.edge = this; 325 if (this.to === this.from) throw "From and to must be distinct."; 326 if (this.from) { 327 if (this.to.time < this.from.time) { 328 console.error("invalid time order"); 329 } 330 let newDepth = this.from.depth + 1; 331 if (this.to.depth > 0 && this.to.depth != newDepth) { 332 console.error("Depth has already been initialized"); 333 } 334 this.to.depth = newDepth; 335 } 336 } 337 } 338 339 chunkIndex(chunks) { 340 // Did anybody say O(n)? 341 for (let i = 0; i < chunks.length; i++) { 342 let chunk = chunks[i]; 343 if (chunk.isEmpty()) continue; 344 if (chunk.last().time < this.time) continue; 345 return i; 346 } 347 return -1; 348 } 349 350 parentEdge() { 351 if (!this.from) return undefined; 352 return this.from.edge; 353 } 354 355 chainLength() { 356 let length = 0; 357 let prev = this; 358 while (prev) { 359 prev = this.parent; 360 length++; 361 } 362 return length; 363 } 364 365 isTransition() { 366 return this.type == "Transition" 367 } 368 369 isFastToSlow() { 370 return this.type == "Normalize" 371 } 372 373 isSlowToFast() { 374 return this.type == "SlowToFast" 375 } 376 377 isInitial() { 378 return this.type == "InitialMap" 379 } 380 381 isReplaceDescriptors() { 382 return this.type == "ReplaceDescriptors" 383 } 384 385 isCopyAsPrototype() { 386 return this.reason == "CopyAsPrototype" 387 } 388 389 isOptimizeAsPrototype() { 390 return this.reason == "OptimizeAsPrototype" 391 } 392 393 symbol() { 394 if (this.isTransition()) return "+"; 395 if (this.isFastToSlow()) return "⊡"; 396 if (this.isSlowToFast()) return "⊛"; 397 if (this.isReplaceDescriptors()) { 398 if (this.name) return "+"; 399 return "∥"; 400 } 401 return ""; 402 } 403 404 toString() { 405 let s = this.symbol(); 406 if (this.isTransition()) return s + this.name; 407 if (this.isFastToSlow()) return s + this.reason; 408 if (this.isCopyAsPrototype()) return s + "Copy as Prototype"; 409 if (this.isOptimizeAsPrototype()) { 410 return s + "Optimize as Prototype"; 411 } 412 if (this.isReplaceDescriptors() && this.name) { 413 return this.type + " " + this.symbol() + this.name; 414 } 415 return this.type + " " + (this.reason ? this.reason : "") + " " + 416 (this.name ? this.name : "") 417 } 418} 419 420 421// =========================================================================== 422class Marker { 423 constructor(time, name) { 424 this.time = parseInt(time); 425 this.name = name; 426 } 427} 428 429// =========================================================================== 430class Timeline { 431 constructor() { 432 this.values = []; 433 this.transitions = new Map(); 434 this.markers = []; 435 this.startTime = 0; 436 this.endTime = 0; 437 } 438 439 push(map) { 440 let time = map.time; 441 if (!this.isEmpty() && this.last().time > time) { 442 // Invalid insertion order, might happen without --single-process, 443 // finding insertion point. 444 let insertionPoint = this.find(time); 445 this.values.splice(insertionPoint, map); 446 } else { 447 this.values.push(map); 448 } 449 if (time > 0) { 450 this.endTime = Math.max(this.endTime, time); 451 if (this.startTime === 0) { 452 this.startTime = time; 453 } else { 454 this.startTime = Math.min(this.startTime, time); 455 } 456 } 457 } 458 459 addMarker(time, message) { 460 this.markers.push(new Marker(time, message)); 461 } 462 463 finalize() { 464 let id = 0; 465 this.forEach(map => { 466 if (map.isRoot()) id = map.finalize(id + 1); 467 if (map.edge && map.edge.name) { 468 let edge = map.edge; 469 let list = this.transitions.get(edge.name); 470 if (list === undefined) { 471 this.transitions.set(edge.name, [edge]); 472 } else { 473 list.push(edge); 474 } 475 } 476 }); 477 this.markers.sort((a, b) => b.time - a.time); 478 } 479 480 at(index) { 481 return this.values[index] 482 } 483 484 isEmpty() { 485 return this.size() == 0 486 } 487 488 size() { 489 return this.values.length 490 } 491 492 first() { 493 return this.values.first() 494 } 495 496 last() { 497 return this.values.last() 498 } 499 500 duration() { 501 return this.last().time - this.first().time 502 } 503 504 forEachChunkSize(count, fn) { 505 const increment = this.duration() / count; 506 let currentTime = this.first().time + increment; 507 let index = 0; 508 for (let i = 0; i < count; i++) { 509 let nextIndex = this.find(currentTime, index); 510 let nextTime = currentTime + increment; 511 fn(index, nextIndex, currentTime, nextTime); 512 index = nextIndex 513 currentTime = nextTime; 514 } 515 } 516 517 chunkSizes(count) { 518 let chunks = []; 519 this.forEachChunkSize(count, (start, end) => chunks.push(end - start)); 520 return chunks; 521 } 522 523 chunks(count) { 524 let chunks = []; 525 let emptyMarkers = []; 526 this.forEachChunkSize(count, (start, end, startTime, endTime) => { 527 let items = this.values.slice(start, end); 528 let markers = this.markersAt(startTime, endTime); 529 chunks.push(new Chunk(chunks.length, startTime, endTime, items, markers)); 530 }); 531 return chunks; 532 } 533 534 range(start, end) { 535 const first = this.find(start); 536 if (first < 0) return []; 537 const last = this.find(end, first); 538 return this.values.slice(first, last); 539 } 540 541 find(time, offset = 0) { 542 return this.basicFind(this.values, each => each.time - time, offset); 543 } 544 545 markersAt(startTime, endTime) { 546 let start = this.basicFind(this.markers, each => each.time - startTime); 547 let end = this.basicFind(this.markers, each => each.time - endTime, start); 548 return this.markers.slice(start, end); 549 } 550 551 basicFind(array, cmp, offset = 0) { 552 let min = offset; 553 let max = array.length; 554 while (min < max) { 555 let mid = min + Math.floor((max - min) / 2); 556 let result = cmp(array[mid]); 557 if (result > 0) { 558 max = mid - 1; 559 } else { 560 min = mid + 1; 561 } 562 } 563 return min; 564 } 565 566 count(filter) { 567 return this.values.reduce((sum, each) => { 568 return sum + (filter(each) ? 1 : 0); 569 }, 0); 570 } 571 572 filter(predicate) { 573 return this.values.filter(predicate); 574 } 575 576 filterUniqueTransitions(filter) { 577 // Returns a list of Maps whose parent is not in the list. 578 return this.values.filter(map => { 579 if (!filter(map)) return false; 580 let parent = map.parent(); 581 if (!parent) return true; 582 return !filter(parent); 583 }); 584 } 585 586 depthHistogram() { 587 return this.values.histogram(each => each.depth); 588 } 589 590 fanOutHistogram() { 591 return this.values.histogram(each => each.children.length); 592 } 593 594 forEach(fn) { 595 return this.values.forEach(fn) 596 } 597} 598 599 600// =========================================================================== 601class Chunk { 602 constructor(index, start, end, items, markers) { 603 this.index = index; 604 this.start = start; 605 this.end = end; 606 this.items = items; 607 this.markers = markers 608 this.height = 0; 609 } 610 611 isEmpty() { 612 return this.items.length == 0; 613 } 614 615 last() { 616 return this.at(this.size() - 1); 617 } 618 619 first() { 620 return this.at(0); 621 } 622 623 at(index) { 624 return this.items[index]; 625 } 626 627 size() { 628 return this.items.length; 629 } 630 631 yOffset(map) { 632 // items[0] == oldest map, displayed at the top of the chunk 633 // items[n-1] == youngest map, displayed at the bottom of the chunk 634 return (1 - (this.indexOf(map) + 0.5) / this.size()) * this.height; 635 } 636 637 indexOf(map) { 638 return this.items.indexOf(map); 639 } 640 641 has(map) { 642 if (this.isEmpty()) return false; 643 return this.first().time <= map.time && map.time <= this.last().time; 644 } 645 646 next(chunks) { 647 return this.findChunk(chunks, 1); 648 } 649 650 prev(chunks) { 651 return this.findChunk(chunks, -1); 652 } 653 654 findChunk(chunks, delta) { 655 let i = this.index + delta; 656 let chunk = chunks[i]; 657 while (chunk && chunk.size() == 0) { 658 i += delta; 659 chunk = chunks[i] 660 } 661 return chunk; 662 } 663 664 getTransitionBreakdown() { 665 return BreakDown(this.items, map => map.getType()) 666 } 667 668 getUniqueTransitions() { 669 // Filter out all the maps that have parents within the same chunk. 670 return this.items.filter(map => !map.parent() || !this.has(map.parent())); 671 } 672} 673 674 675// =========================================================================== 676function BreakDown(list, map_fn) { 677 if (map_fn === void 0) { 678 map_fn = each => each; 679 } 680 let breakdown = {__proto__:null}; 681 list.forEach(each=> { 682 let type = map_fn(each); 683 let v = breakdown[type]; 684 breakdown[type] = (v | 0) + 1 685 }); 686 return Object.entries(breakdown) 687 .sort((a,b) => a[1] - b[1]); 688} 689 690 691// =========================================================================== 692class ArgumentsProcessor extends BaseArgumentsProcessor { 693 getArgsDispatch() { 694 return { 695 '--range': ['range', 'auto,auto', 696 'Specify the range limit as [start],[end]' 697 ], 698 '--source-map': ['sourceMap', null, 699 'Specify the source map that should be used for output' 700 ] 701 }; 702 } 703 704 getDefaultResults() { 705 return { 706 logFileName: 'v8.log', 707 range: 'auto,auto', 708 }; 709 } 710} 711