1<!DOCTYPE html> 2<!-- 3Copyright (c) 2012 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/ui/base/color_scheme.html"> 9<link rel="import" href="/base/quad.html"> 10<link rel="import" href="/base/range.html"> 11<link rel="import" href="/base/units/time.html"> 12<link rel="import" href="/importer/importer.html"> 13<link rel="import" href="/model/attribute.html"> 14<link rel="import" href="/model/comment_box_annotation.html"> 15<link rel="import" href="/model/instant_event.html"> 16<link rel="import" href="/model/flow_event.html"> 17<link rel="import" href="/model/counter_series.html"> 18<link rel="import" href="/model/slice_group.html"> 19<link rel="import" href="/model/global_memory_dump.html"> 20<link rel="import" href="/model/memory_allocator_dump.html"> 21<link rel="import" href="/model/process_memory_dump.html"> 22<link rel="import" href="/model/rect_annotation.html"> 23<link rel="import" href="/model/x_marker_annotation.html"> 24<link rel="import" href="/model/model.html"> 25 26<script> 27'use strict'; 28 29/** 30 * @fileoverview TraceEventImporter imports TraceEvent-formatted data 31 * into the provided model. 32 */ 33tr.exportTo('tr.e.importer', function() { 34 35 var Importer = tr.importer.Importer; 36 37 function deepCopy(value) { 38 if (!(value instanceof Object)) { 39 if (value === undefined || value === null) 40 return value; 41 if (typeof value == 'string') 42 return value.substring(); 43 if (typeof value == 'boolean') 44 return value; 45 if (typeof value == 'number') 46 return value; 47 throw new Error('Unrecognized: ' + typeof value); 48 } 49 50 var object = value; 51 if (object instanceof Array) { 52 var res = new Array(object.length); 53 for (var i = 0; i < object.length; i++) 54 res[i] = deepCopy(object[i]); 55 return res; 56 } 57 58 if (object.__proto__ != Object.prototype) 59 throw new Error('Can only clone simple types'); 60 var res = {}; 61 for (var key in object) { 62 res[key] = deepCopy(object[key]); 63 } 64 return res; 65 } 66 67 function getEventColor(event, opt_customName) { 68 if (event.cname) 69 return tr.ui.b.getColorIdForReservedName(event.cname); 70 else if (opt_customName || event.name) { 71 return tr.ui.b.getColorIdForGeneralPurposeString( 72 opt_customName || event.name); 73 } 74 } 75 76 var timestampFromUs = tr.b.units.Time.timestampFromUs; 77 var maybeTimestampFromUs = tr.b.units.Time.maybeTimestampFromUs; 78 79 function TraceEventImporter(model, eventData) { 80 this.importPriority = 1; 81 this.model_ = model; 82 this.events_ = undefined; 83 this.sampleEvents_ = undefined; 84 this.stackFrameEvents_ = undefined; 85 this.systemTraceEvents_ = undefined; 86 this.battorData_ = undefined; 87 this.eventsWereFromString_ = false; 88 this.softwareMeasuredCpuCount_ = undefined; 89 this.allAsyncEvents_ = []; 90 this.allFlowEvents_ = []; 91 this.allObjectEvents_ = []; 92 this.traceEventSampleStackFramesByName_ = {}; 93 94 // Dump ID -> {global: (event | undefined), process: [events]} 95 this.allMemoryDumpEvents_ = {}; 96 97 if (typeof(eventData) === 'string' || eventData instanceof String) { 98 eventData = eventData.trim(); 99 // If the event data begins with a [, then we know it should end with a ]. 100 // The reason we check for this is because some tracing implementations 101 // cannot guarantee that a ']' gets written to the trace file. So, we are 102 // forgiving and if this is obviously the case, we fix it up before 103 // throwing the string at JSON.parse. 104 if (eventData[0] === '[') { 105 eventData = eventData.replace(/\s*,\s*$/, ''); 106 if (eventData[eventData.length - 1] !== ']') 107 eventData = eventData + ']'; 108 } 109 110 this.events_ = JSON.parse(eventData); 111 this.eventsWereFromString_ = true; 112 } else { 113 this.events_ = eventData; 114 } 115 116 this.traceAnnotations_ = this.events_.traceAnnotations; 117 118 // Some trace_event implementations put the actual trace events 119 // inside a container. E.g { ... , traceEvents: [ ] } 120 // If we see that, just pull out the trace events. 121 if (this.events_.traceEvents) { 122 var container = this.events_; 123 this.events_ = this.events_.traceEvents; 124 125 // Some trace_event implementations put ftrace_importer traces as a 126 // huge string inside container.systemTraceEvents. If we see that, pull it 127 // out. It will be picked up by extractSubtraces later on. 128 this.systemTraceEvents_ = container.systemTraceEvents; 129 130 // Some trace_event implementations put battor power traces as a 131 // huge string inside container.battorLogAsString. If we see that, pull 132 // it out. It will be picked up by extractSubtraces later on. 133 this.battorData_ = container.battorLogAsString; 134 135 // Sampling data. 136 this.sampleEvents_ = container.samples; 137 this.stackFrameEvents_ = container.stackFrames; 138 139 // Some implementations specify displayTimeUnit 140 if (container.displayTimeUnit) { 141 var unitName = container.displayTimeUnit; 142 var unit = tr.b.units.Time.supportedUnits[unitName]; 143 if (unit === undefined) { 144 throw new Error('Unit ' + unitName + ' is not supported.'); 145 } 146 this.model_.intrinsicTimeUnit = unit; 147 } 148 149 var knownFieldNames = { 150 battorLogAsString: true, 151 samples: true, 152 stackFrames: true, 153 systemTraceEvents: true, 154 traceAnnotations: true, 155 traceEvents: true 156 }; 157 // Any other fields in the container should be treated as metadata. 158 for (var fieldName in container) { 159 if (fieldName in knownFieldNames) 160 continue; 161 this.model_.metadata.push({name: fieldName, 162 value: container[fieldName]}); 163 } 164 } 165 } 166 167 /** 168 * @return {boolean} Whether obj is a TraceEvent array. 169 */ 170 TraceEventImporter.canImport = function(eventData) { 171 // May be encoded JSON. But we dont want to parse it fully yet. 172 // Use a simple heuristic: 173 // - eventData that starts with [ are probably trace_event 174 // - eventData that starts with { are probably trace_event 175 // May be encoded JSON. Treat files that start with { as importable by us. 176 if (typeof(eventData) === 'string' || eventData instanceof String) { 177 eventData = eventData.trim(); 178 return eventData[0] == '{' || eventData[0] == '['; 179 } 180 181 // Might just be an array of events 182 if (eventData instanceof Array && eventData.length && eventData[0].ph) 183 return true; 184 185 // Might be an object with a traceEvents field in it. 186 if (eventData.traceEvents) { 187 if (eventData.traceEvents instanceof Array) { 188 if (eventData.traceEvents.length && eventData.traceEvents[0].ph) 189 return true; 190 if (eventData.samples.length && eventData.stackFrames !== undefined) 191 return true; 192 } 193 } 194 195 return false; 196 }; 197 198 TraceEventImporter.prototype = { 199 200 __proto__: Importer.prototype, 201 202 extractSubtraces: function() { 203 var systemEventsTmp = this.systemTraceEvents_; 204 var battorDataTmp = this.battorData_; 205 this.systemTraceEvents_ = undefined; 206 this.battorData_ = undefined; 207 var subTraces = systemEventsTmp ? [systemEventsTmp] : []; 208 if (battorDataTmp) 209 subTraces.push(battorDataTmp); 210 return subTraces; 211 }, 212 213 /** 214 * Deep copying is only needed if the trace was given to us as events. 215 */ 216 deepCopyIfNeeded_: function(obj) { 217 if (obj === undefined) 218 obj = {}; 219 if (this.eventsWereFromString_) 220 return obj; 221 return deepCopy(obj); 222 }, 223 224 /** 225 * Helper to process an async event. 226 */ 227 processAsyncEvent: function(event) { 228 var thread = this.model_.getOrCreateProcess(event.pid). 229 getOrCreateThread(event.tid); 230 this.allAsyncEvents_.push({ 231 sequenceNumber: this.allAsyncEvents_.length, 232 event: event, 233 thread: thread}); 234 }, 235 236 /** 237 * Helper to process a flow event. 238 */ 239 processFlowEvent: function(event) { 240 var thread = this.model_.getOrCreateProcess(event.pid). 241 getOrCreateThread(event.tid); 242 this.allFlowEvents_.push({ 243 refGuid: tr.b.GUID.getLastGuid(), 244 sequenceNumber: this.allFlowEvents_.length, 245 event: event, 246 thread: thread 247 }); 248 }, 249 250 /** 251 * Helper that creates and adds samples to a Counter object based on 252 * 'C' phase events. 253 */ 254 processCounterEvent: function(event) { 255 var ctr_name; 256 if (event.id !== undefined) 257 ctr_name = event.name + '[' + event.id + ']'; 258 else 259 ctr_name = event.name; 260 261 var ctr = this.model_.getOrCreateProcess(event.pid) 262 .getOrCreateCounter(event.cat, ctr_name); 263 var reservedColorId = event.cname ? getEventColor(event) : undefined; 264 265 // Initialize the counter's series fields if needed. 266 if (ctr.numSeries === 0) { 267 for (var seriesName in event.args) { 268 var colorId = reservedColorId || 269 getEventColor(event, ctr.name + '.' + seriesName); 270 ctr.addSeries(new tr.model.CounterSeries(seriesName, colorId)); 271 } 272 273 if (ctr.numSeries === 0) { 274 this.model_.importWarning({ 275 type: 'counter_parse_error', 276 message: 'Expected counter ' + event.name + 277 ' to have at least one argument to use as a value.' 278 }); 279 280 // Drop the counter. 281 delete ctr.parent.counters[ctr.name]; 282 return; 283 } 284 } 285 286 var ts = timestampFromUs(event.ts); 287 ctr.series.forEach(function(series) { 288 var val = event.args[series.name] ? event.args[series.name] : 0; 289 series.addCounterSample(ts, val); 290 }); 291 }, 292 293 processObjectEvent: function(event) { 294 var thread = this.model_.getOrCreateProcess(event.pid). 295 getOrCreateThread(event.tid); 296 this.allObjectEvents_.push({ 297 sequenceNumber: this.allObjectEvents_.length, 298 event: event, 299 thread: thread}); 300 }, 301 302 processDurationEvent: function(event) { 303 var thread = this.model_.getOrCreateProcess(event.pid) 304 .getOrCreateThread(event.tid); 305 var ts = timestampFromUs(event.ts); 306 if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)) { 307 this.model_.importWarning({ 308 type: 'duration_parse_error', 309 message: 'Timestamps are moving backward.' 310 }); 311 return; 312 } 313 314 if (event.ph == 'B') { 315 var slice = thread.sliceGroup.beginSlice( 316 event.cat, event.name, timestampFromUs(event.ts), 317 this.deepCopyIfNeeded_(event.args), 318 timestampFromUs(event.tts), event.argsStripped, 319 getEventColor(event)); 320 slice.startStackFrame = this.getStackFrameForEvent_(event); 321 } else if (event.ph == 'I' || event.ph == 'i') { 322 if (event.s !== undefined && event.s !== 't') 323 throw new Error('This should never happen'); 324 325 thread.sliceGroup.beginSlice(event.cat, event.name, 326 timestampFromUs(event.ts), 327 this.deepCopyIfNeeded_(event.args), 328 timestampFromUs(event.tts), 329 event.argsStripped, 330 getEventColor(event)); 331 var slice = thread.sliceGroup.endSlice(timestampFromUs(event.ts), 332 timestampFromUs(event.tts)); 333 slice.startStackFrame = this.getStackFrameForEvent_(event); 334 slice.endStackFrame = undefined; 335 } else { 336 if (!thread.sliceGroup.openSliceCount) { 337 this.model_.importWarning({ 338 type: 'duration_parse_error', 339 message: 'E phase event without a matching B phase event.' 340 }); 341 return; 342 } 343 344 var slice = thread.sliceGroup.endSlice(timestampFromUs(event.ts), 345 timestampFromUs(event.tts), 346 getEventColor(event)); 347 if (event.name && slice.title != event.name) { 348 this.model_.importWarning({ 349 type: 'title_match_error', 350 message: 'Titles do not match. Title is ' + 351 slice.title + ' in openSlice, and is ' + 352 event.name + ' in endSlice' 353 }); 354 } 355 slice.endStackFrame = this.getStackFrameForEvent_(event); 356 357 this.mergeArgsInto_(slice.args, event.args, slice.title); 358 } 359 }, 360 361 mergeArgsInto_: function(dstArgs, srcArgs, eventName) { 362 for (var arg in srcArgs) { 363 if (dstArgs[arg] !== undefined) { 364 this.model_.importWarning({ 365 type: 'arg_merge_error', 366 message: 'Different phases of ' + eventName + 367 ' provided values for argument ' + arg + '.' + 368 ' The last provided value will be used.' 369 }); 370 } 371 dstArgs[arg] = this.deepCopyIfNeeded_(srcArgs[arg]); 372 } 373 }, 374 375 processCompleteEvent: function(event) { 376 var thread = this.model_.getOrCreateProcess(event.pid) 377 .getOrCreateThread(event.tid); 378 var slice = thread.sliceGroup.pushCompleteSlice(event.cat, event.name, 379 timestampFromUs(event.ts), 380 maybeTimestampFromUs(event.dur), 381 maybeTimestampFromUs(event.tts), 382 maybeTimestampFromUs(event.tdur), 383 this.deepCopyIfNeeded_(event.args), 384 event.argsStripped, 385 getEventColor(event)); 386 slice.startStackFrame = this.getStackFrameForEvent_(event); 387 slice.endStackFrame = this.getStackFrameForEvent_(event, true); 388 }, 389 390 processMetadataEvent: function(event) { 391 // The metadata events aren't useful without args. 392 if (event.argsStripped) 393 return; 394 395 if (event.name == 'process_name') { 396 var process = this.model_.getOrCreateProcess(event.pid); 397 process.name = event.args.name; 398 } else if (event.name == 'process_labels') { 399 var process = this.model_.getOrCreateProcess(event.pid); 400 var labels = event.args.labels.split(','); 401 for (var i = 0; i < labels.length; i++) 402 process.addLabelIfNeeded(labels[i]); 403 } else if (event.name == 'process_sort_index') { 404 var process = this.model_.getOrCreateProcess(event.pid); 405 process.sortIndex = event.args.sort_index; 406 } else if (event.name == 'thread_name') { 407 var thread = this.model_.getOrCreateProcess(event.pid). 408 getOrCreateThread(event.tid); 409 thread.name = event.args.name; 410 } else if (event.name == 'thread_sort_index') { 411 var thread = this.model_.getOrCreateProcess(event.pid). 412 getOrCreateThread(event.tid); 413 thread.sortIndex = event.args.sort_index; 414 } else if (event.name == 'num_cpus') { 415 var n = event.args.number; 416 // Not all render processes agree on the cpu count in trace_event. Some 417 // processes will report 1, while others will report the actual cpu 418 // count. To deal with this, take the max of what is reported. 419 if (this.softwareMeasuredCpuCount_ !== undefined) 420 n = Math.max(n, this.softwareMeasuredCpuCount_); 421 this.softwareMeasuredCpuCount_ = n; 422 } else { 423 this.model_.importWarning({ 424 type: 'metadata_parse_error', 425 message: 'Unrecognized metadata name: ' + event.name 426 }); 427 } 428 }, 429 430 processInstantEvent: function(event) { 431 // Thread-level instant events are treated as zero-duration slices. 432 if (event.s == 't' || event.s === undefined) { 433 this.processDurationEvent(event); 434 return; 435 } 436 437 var constructor; 438 switch (event.s) { 439 case 'g': 440 constructor = tr.model.GlobalInstantEvent; 441 break; 442 case 'p': 443 constructor = tr.model.ProcessInstantEvent; 444 break; 445 default: 446 this.model_.importWarning({ 447 type: 'instant_parse_error', 448 message: 'I phase event with unknown "s" field value.' 449 }); 450 return; 451 } 452 453 var instantEvent = new constructor(event.cat, event.name, 454 getEventColor(event), timestampFromUs(event.ts), 455 this.deepCopyIfNeeded_(event.args)); 456 457 switch (instantEvent.type) { 458 case tr.model.InstantEventType.GLOBAL: 459 this.model_.pushInstantEvent(instantEvent); 460 break; 461 462 case tr.model.InstantEventType.PROCESS: 463 var process = this.model_.getOrCreateProcess(event.pid); 464 process.pushInstantEvent(instantEvent); 465 break; 466 467 default: 468 throw new Error('Unknown instant event type: ' + event.s); 469 } 470 }, 471 472 processTraceSampleEvent: function(event) { 473 var thread = this.model_.getOrCreateProcess(event.pid) 474 .getOrCreateThread(event.tid); 475 476 var stackFrame = this.getStackFrameForEvent_(event); 477 if (stackFrame === undefined) { 478 stackFrame = this.traceEventSampleStackFramesByName_[ 479 event.name]; 480 } 481 if (stackFrame === undefined) { 482 var id = 'te-' + tr.b.GUID.allocate(); 483 stackFrame = new tr.model.StackFrame( 484 undefined, id, 485 event.cat, event.name, 486 tr.ui.b.getColorIdForGeneralPurposeString(event.name)); 487 this.model_.addStackFrame(stackFrame); 488 this.traceEventSampleStackFramesByName_[event.name] = stackFrame; 489 } 490 491 var sample = new tr.model.Sample( 492 undefined, thread, 'TRACE_EVENT_SAMPLE', 493 timestampFromUs(event.ts), stackFrame, 1, 494 this.deepCopyIfNeeded_(event.args)); 495 this.model_.samples.push(sample); 496 }, 497 498 getOrCreateMemoryDumpEvents_: function(dumpId) { 499 if (this.allMemoryDumpEvents_[dumpId] === undefined) { 500 this.allMemoryDumpEvents_[dumpId] = { 501 global: undefined, 502 process: [] 503 }; 504 } 505 return this.allMemoryDumpEvents_[dumpId]; 506 }, 507 508 processMemoryDumpEvent: function(event) { 509 if (event.id === undefined) { 510 this.model_.importWarning({ 511 type: 'memory_dump_parse_error', 512 message: event.ph + ' phase event without a dump ID.' 513 }); 514 return; 515 } 516 var events = this.getOrCreateMemoryDumpEvents_(event.id); 517 518 if (event.ph === 'v') { 519 // Add a process memory dump. 520 events.process.push(event); 521 } else if (event.ph === 'V') { 522 // Add a global memory dump (unless already present). 523 if (events.global !== undefined) { 524 this.model_.importWarning({ 525 type: 'memory_dump_parse_error', 526 message: 'Multiple V phase events with the same dump ID.' 527 }); 528 return; 529 } 530 events.global = event; 531 } else { 532 throw new Error('Invalid memory dump event phase "' + event.ph + '".'); 533 } 534 }, 535 536 /** 537 * Walks through the events_ list and outputs the structures discovered to 538 * model_. 539 */ 540 importEvents: function() { 541 var csr = new tr.ClockSyncRecord('ftrace_importer', 0, {}); 542 this.model_.clockSyncRecords.push(csr); 543 if (this.stackFrameEvents_) 544 this.importStackFrames_(); 545 546 if (this.traceAnnotations_) 547 this.importAnnotations_(); 548 549 var events = this.events_; 550 for (var eI = 0; eI < events.length; eI++) { 551 var event = events[eI]; 552 if (event.args === '__stripped__') { 553 event.argsStripped = true; 554 event.args = undefined; 555 } 556 557 if (event.ph === 'B' || event.ph === 'E') { 558 this.processDurationEvent(event); 559 560 } else if (event.ph === 'X') { 561 this.processCompleteEvent(event); 562 563 } else if (event.ph === 'b' || event.ph === 'e' || event.ph === 'n' || 564 event.ph === 'S' || event.ph === 'F' || event.ph === 'T' || 565 event.ph === 'p') { 566 this.processAsyncEvent(event); 567 568 // Note, I is historic. The instant event marker got changed, but we 569 // want to support loading old trace files so we have both I and i. 570 } else if (event.ph == 'I' || event.ph == 'i') { 571 this.processInstantEvent(event); 572 573 } else if (event.ph == 'P') { 574 this.processTraceSampleEvent(event); 575 576 } else if (event.ph == 'C') { 577 this.processCounterEvent(event); 578 579 } else if (event.ph == 'M') { 580 this.processMetadataEvent(event); 581 582 } else if (event.ph === 'N' || event.ph === 'D' || event.ph === 'O') { 583 this.processObjectEvent(event); 584 585 } else if (event.ph === 's' || event.ph === 't' || event.ph === 'f') { 586 this.processFlowEvent(event); 587 588 } else if (event.ph === 'v' || event.ph === 'V') { 589 this.processMemoryDumpEvent(event); 590 591 } else { 592 this.model_.importWarning({ 593 type: 'parse_error', 594 message: 'Unrecognized event phase: ' + 595 event.ph + ' (' + event.name + ')' 596 }); 597 } 598 } 599 }, 600 601 importStackFrames_: function() { 602 var m = this.model_; 603 var events = this.stackFrameEvents_; 604 605 for (var id in events) { 606 var event = events[id]; 607 var textForColor = event.category ? event.category : event.name; 608 var frame = new tr.model.StackFrame( 609 undefined, 'g' + id, 610 event.category, event.name, 611 tr.ui.b.getColorIdForGeneralPurposeString(textForColor)); 612 m.addStackFrame(frame); 613 } 614 for (var id in events) { 615 var event = events[id]; 616 if (event.parent === undefined) 617 continue; 618 619 var frame = m.stackFrames['g' + id]; 620 if (frame === undefined) 621 throw new Error('omg'); 622 var parentFrame; 623 if (event.parent === undefined) { 624 parentFrame = undefined; 625 } else { 626 parentFrame = m.stackFrames['g' + event.parent]; 627 if (parentFrame === undefined) 628 throw new Error('omg'); 629 } 630 frame.parentFrame = parentFrame; 631 } 632 }, 633 634 importAnnotations_: function() { 635 for (var id in this.traceAnnotations_) { 636 var annotation = tr.model.Annotation.fromDictIfPossible( 637 this.traceAnnotations_[id]); 638 if (!annotation) { 639 this.model_.importWarning({ 640 type: 'annotation_warning', 641 message: 'Unrecognized traceAnnotation typeName \"' + 642 this.traceAnnotations_[id].typeName + '\"' 643 }); 644 continue; 645 } 646 this.model_.addAnnotation(annotation); 647 } 648 }, 649 650 /** 651 * Called by the Model after all other importers have imported their 652 * events. 653 */ 654 finalizeImport: function() { 655 if (this.softwareMeasuredCpuCount_ !== undefined) { 656 this.model_.kernel.softwareMeasuredCpuCount = 657 this.softwareMeasuredCpuCount_; 658 } 659 this.createAsyncSlices_(); 660 this.createFlowSlices_(); 661 this.createExplicitObjects_(); 662 this.createImplicitObjects_(); 663 this.createMemoryDumps_(); 664 }, 665 666 /* Events can have one or more stack frames associated with them, but 667 * that frame might be encoded either as a stack trace of program counters, 668 * or as a direct stack frame reference. This handles either case and 669 * if found, returns the stackframe. 670 */ 671 getStackFrameForEvent_: function(event, opt_lookForEndEvent) { 672 var sf; 673 var stack; 674 if (opt_lookForEndEvent) { 675 sf = event.esf; 676 stack = event.estack; 677 } else { 678 sf = event.sf; 679 stack = event.stack; 680 } 681 if (stack !== undefined && sf !== undefined) { 682 this.model_.importWarning({ 683 type: 'stack_frame_and_stack_error', 684 message: 'Event at ' + event.ts + 685 ' cannot have both a stack and a stackframe.' 686 }); 687 return undefined; 688 } 689 690 if (stack !== undefined) 691 return this.model_.resolveStackToStackFrame_(event.pid, stack); 692 if (sf === undefined) 693 return undefined; 694 695 var stackFrame = this.model_.stackFrames['g' + sf]; 696 if (stackFrame === undefined) { 697 this.model_.importWarning({ 698 type: 'sample_import_error', 699 message: 'No frame for ' + sf 700 }); 701 return; 702 } 703 return stackFrame; 704 }, 705 706 resolveStackToStackFrame_: function(pid, stack) { 707 // TODO(alph,fmeawad): Add codemap resolution code here. 708 return undefined; 709 }, 710 711 importSampleData: function() { 712 if (!this.sampleEvents_) 713 return; 714 var m = this.model_; 715 716 // If this is the only importer, then fake-create the threads. 717 var events = this.sampleEvents_; 718 if (this.events_.length === 0) { 719 for (var i = 0; i < events.length; i++) { 720 var event = events[i]; 721 m.getOrCreateProcess(event.tid).getOrCreateThread(event.tid); 722 } 723 } 724 725 var threadsByTid = {}; 726 m.getAllThreads().forEach(function(t) { 727 threadsByTid[t.tid] = t; 728 }); 729 730 for (var i = 0; i < events.length; i++) { 731 var event = events[i]; 732 var thread = threadsByTid[event.tid]; 733 if (thread === undefined) { 734 m.importWarning({ 735 type: 'sample_import_error', 736 message: 'Thread ' + events.tid + 'not found' 737 }); 738 continue; 739 } 740 741 var cpu; 742 if (event.cpu !== undefined) 743 cpu = m.kernel.getOrCreateCpu(event.cpu); 744 745 var stackFrame = this.getStackFrameForEvent_(event); 746 747 var sample = new tr.model.Sample( 748 cpu, thread, 749 event.name, 750 timestampFromUs(event.ts), 751 stackFrame, 752 event.weight); 753 m.samples.push(sample); 754 } 755 }, 756 757 /** 758 * Called by the model to join references between objects, after final model 759 * bounds have been computed. 760 */ 761 joinRefs: function() { 762 this.joinObjectRefs_(); 763 }, 764 765 createAsyncSlices_: function() { 766 if (this.allAsyncEvents_.length === 0) 767 return; 768 769 this.allAsyncEvents_.sort(function(x, y) { 770 var d = x.event.ts - y.event.ts; 771 if (d !== 0) 772 return d; 773 return x.sequenceNumber - y.sequenceNumber; 774 }); 775 776 var legacyEvents = []; 777 // Group nestable async events by ID. Events with the same ID should 778 // belong to the same parent async event. 779 var nestableAsyncEventsByKey = {}; 780 for (var i = 0; i < this.allAsyncEvents_.length; i++) { 781 var asyncEventState = this.allAsyncEvents_[i]; 782 var event = asyncEventState.event; 783 if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' || 784 event.ph === 'p') { 785 legacyEvents.push(asyncEventState); 786 continue; 787 } 788 if (event.cat === undefined) { 789 this.model_.importWarning({ 790 type: 'async_slice_parse_error', 791 message: 'Nestable async events (ph: b, e, or n) require a ' + 792 'cat parameter.' 793 }); 794 continue; 795 } 796 797 if (event.name === undefined) { 798 this.model_.importWarning({ 799 type: 'async_slice_parse_error', 800 message: 'Nestable async events (ph: b, e, or n) require a ' + 801 'name parameter.' 802 }); 803 continue; 804 } 805 806 if (event.id === undefined) { 807 this.model_.importWarning({ 808 type: 'async_slice_parse_error', 809 message: 'Nestable async events (ph: b, e, or n) require an ' + 810 'id parameter.' 811 }); 812 continue; 813 } 814 var key = event.cat + ':' + event.id; 815 if (nestableAsyncEventsByKey[key] === undefined) 816 nestableAsyncEventsByKey[key] = []; 817 nestableAsyncEventsByKey[key].push(asyncEventState); 818 } 819 // Handle legacy async events. 820 this.createLegacyAsyncSlices_(legacyEvents); 821 822 // Parse nestable async events into AsyncSlices. 823 for (var key in nestableAsyncEventsByKey) { 824 var eventStateEntries = nestableAsyncEventsByKey[key]; 825 // Stack of enclosing BEGIN events. 826 var parentStack = []; 827 for (var i = 0; i < eventStateEntries.length; ++i) { 828 var eventStateEntry = eventStateEntries[i]; 829 // If this is the end of an event, match it to the start. 830 if (eventStateEntry.event.ph === 'e') { 831 // Walk up the parent stack to find the corresponding BEGIN for 832 // this END. 833 var parentIndex = -1; 834 for (var k = parentStack.length - 1; k >= 0; --k) { 835 if (parentStack[k].event.name === eventStateEntry.event.name) { 836 parentIndex = k; 837 break; 838 } 839 } 840 if (parentIndex === -1) { 841 // Unmatched end. 842 eventStateEntry.finished = false; 843 } else { 844 parentStack[parentIndex].end = eventStateEntry; 845 // Pop off all enclosing unmatched BEGINs util parentIndex. 846 while (parentIndex < parentStack.length) { 847 parentStack.pop(); 848 } 849 } 850 } 851 // Inherit the current parent. 852 if (parentStack.length > 0) 853 eventStateEntry.parentEntry = parentStack[parentStack.length - 1]; 854 if (eventStateEntry.event.ph === 'b') 855 parentStack.push(eventStateEntry); 856 } 857 var topLevelSlices = []; 858 for (var i = 0; i < eventStateEntries.length; ++i) { 859 var eventStateEntry = eventStateEntries[i]; 860 // Skip matched END, as its slice will be created when we 861 // encounter its corresponding BEGIN. 862 if (eventStateEntry.event.ph === 'e' && 863 eventStateEntry.finished === undefined) { 864 continue; 865 } 866 var startState = undefined; 867 var endState = undefined; 868 var sliceArgs = eventStateEntry.event.args || {}; 869 var sliceError = undefined; 870 if (eventStateEntry.event.ph === 'n') { 871 startState = eventStateEntry; 872 endState = eventStateEntry; 873 } else if (eventStateEntry.event.ph === 'b') { 874 if (eventStateEntry.end === undefined) { 875 // Unmatched BEGIN. End it when last event with this ID ends. 876 eventStateEntry.end = 877 eventStateEntries[eventStateEntries.length - 1]; 878 sliceError = 879 'Slice has no matching END. End time has been adjusted.'; 880 this.model_.importWarning({ 881 type: 'async_slice_parse_error', 882 message: 'Nestable async BEGIN event at ' + 883 eventStateEntry.event.ts + ' with name=' + 884 eventStateEntry.event.name + 885 ' and id=' + eventStateEntry.event.id + ' was unmatched.' 886 }); 887 } else { 888 // Include args for both END and BEGIN for a matched pair. 889 var concatenateArguments = function(args1, args2) { 890 if (args1.params === undefined || args2.params === undefined) 891 return tr.b.concatenateObjects(args1, args2); 892 // Make an argument object to hold the combined params. 893 var args3 = {}; 894 args3.params = tr.b.concatenateObjects(args1.params, 895 args2.params); 896 return tr.b.concatenateObjects(args1, args2, args3); 897 } 898 var endArgs = eventStateEntry.end.event.args || {}; 899 sliceArgs = concatenateArguments(sliceArgs, endArgs); 900 } 901 startState = eventStateEntry; 902 endState = eventStateEntry.end; 903 } else { 904 // Unmatched END. Start it at the first event with this ID starts. 905 sliceError = 906 'Slice has no matching BEGIN. Start time has been adjusted.'; 907 this.model_.importWarning({ 908 type: 'async_slice_parse_error', 909 message: 'Nestable async END event at ' + 910 eventStateEntry.event.ts + ' with name=' + 911 eventStateEntry.event.name + 912 ' and id=' + eventStateEntry.event.id + ' was unmatched.' 913 }); 914 startState = eventStateEntries[0]; 915 endState = eventStateEntry; 916 } 917 918 var isTopLevel = (eventStateEntry.parentEntry === undefined); 919 var asyncSliceConstructor = 920 tr.model.AsyncSlice.getConstructor( 921 eventStateEntry.event.cat, 922 eventStateEntry.event.name); 923 924 var thread_start = undefined; 925 var thread_duration = undefined; 926 if (startState.event.tts && startState.event.use_async_tts) { 927 thread_start = timestampFromUs(startState.event.tts); 928 if (endState.event.tts) { 929 var thread_end = timestampFromUs(endState.event.tts); 930 thread_duration = thread_end - thread_start; 931 } 932 } 933 934 var slice = new asyncSliceConstructor( 935 eventStateEntry.event.cat, 936 eventStateEntry.event.name, 937 getEventColor(endState.event), 938 timestampFromUs(startState.event.ts), 939 sliceArgs, 940 timestampFromUs(endState.event.ts - startState.event.ts), 941 isTopLevel, 942 thread_start, 943 thread_duration, 944 startState.event.argsStripped); 945 946 slice.startThread = startState.thread; 947 slice.endThread = endState.thread; 948 slice.id = key; 949 if (sliceError !== undefined) 950 slice.error = sliceError; 951 eventStateEntry.slice = slice; 952 // Add the slice to the topLevelSlices array if there is no parent. 953 // Otherwise, add the slice to the subSlices of its parent. 954 if (isTopLevel) { 955 topLevelSlices.push(slice); 956 } else if (eventStateEntry.parentEntry.slice !== undefined) { 957 eventStateEntry.parentEntry.slice.subSlices.push(slice); 958 } 959 } 960 for (var si = 0; si < topLevelSlices.length; si++) { 961 topLevelSlices[si].startThread.asyncSliceGroup.push( 962 topLevelSlices[si]); 963 } 964 } 965 }, 966 967 createLegacyAsyncSlices_: function(legacyEvents) { 968 if (legacyEvents.length === 0) 969 return; 970 971 legacyEvents.sort(function(x, y) { 972 var d = x.event.ts - y.event.ts; 973 if (d != 0) 974 return d; 975 return x.sequenceNumber - y.sequenceNumber; 976 }); 977 978 var asyncEventStatesByNameThenID = {}; 979 980 for (var i = 0; i < legacyEvents.length; i++) { 981 var asyncEventState = legacyEvents[i]; 982 983 var event = asyncEventState.event; 984 var name = event.name; 985 if (name === undefined) { 986 this.model_.importWarning({ 987 type: 'async_slice_parse_error', 988 message: 'Async events (ph: S, T, p, or F) require a name ' + 989 ' parameter.' 990 }); 991 continue; 992 } 993 994 var id = event.id; 995 if (id === undefined) { 996 this.model_.importWarning({ 997 type: 'async_slice_parse_error', 998 message: 'Async events (ph: S, T, p, or F) require an id parameter.' 999 }); 1000 continue; 1001 } 1002 1003 // TODO(simonjam): Add a synchronous tick on the appropriate thread. 1004 1005 if (event.ph === 'S') { 1006 if (asyncEventStatesByNameThenID[name] === undefined) 1007 asyncEventStatesByNameThenID[name] = {}; 1008 if (asyncEventStatesByNameThenID[name][id]) { 1009 this.model_.importWarning({ 1010 type: 'async_slice_parse_error', 1011 message: 'At ' + event.ts + ', a slice of the same id ' + id + 1012 ' was alrady open.' 1013 }); 1014 continue; 1015 } 1016 asyncEventStatesByNameThenID[name][id] = []; 1017 asyncEventStatesByNameThenID[name][id].push(asyncEventState); 1018 } else { 1019 if (asyncEventStatesByNameThenID[name] === undefined) { 1020 this.model_.importWarning({ 1021 type: 'async_slice_parse_error', 1022 message: 'At ' + event.ts + ', no slice named ' + name + 1023 ' was open.' 1024 }); 1025 continue; 1026 } 1027 if (asyncEventStatesByNameThenID[name][id] === undefined) { 1028 this.model_.importWarning({ 1029 type: 'async_slice_parse_error', 1030 message: 'At ' + event.ts + ', no slice named ' + name + 1031 ' with id=' + id + ' was open.' 1032 }); 1033 continue; 1034 } 1035 var events = asyncEventStatesByNameThenID[name][id]; 1036 events.push(asyncEventState); 1037 1038 if (event.ph === 'F') { 1039 // Create a slice from start to end. 1040 var asyncSliceConstructor = 1041 tr.model.AsyncSlice.getConstructor( 1042 events[0].event.cat, 1043 name); 1044 var slice = new asyncSliceConstructor( 1045 events[0].event.cat, 1046 name, 1047 getEventColor(events[0].event), 1048 timestampFromUs(events[0].event.ts), 1049 tr.b.concatenateObjects(events[0].event.args, 1050 events[events.length - 1].event.args), 1051 timestampFromUs(event.ts - events[0].event.ts), 1052 true, undefined, undefined, events[0].event.argsStripped); 1053 slice.startThread = events[0].thread; 1054 slice.endThread = asyncEventState.thread; 1055 slice.id = id; 1056 1057 var stepType = events[1].event.ph; 1058 var isValid = true; 1059 1060 // Create subSlices for each step. Skip the start and finish events, 1061 // which are always first and last respectively. 1062 for (var j = 1; j < events.length - 1; ++j) { 1063 if (events[j].event.ph === 'T' || events[j].event.ph === 'p') { 1064 isValid = this.assertStepTypeMatches_(stepType, events[j]); 1065 if (!isValid) 1066 break; 1067 } 1068 1069 if (events[j].event.ph === 'S') { 1070 this.model_.importWarning({ 1071 type: 'async_slice_parse_error', 1072 message: 'At ' + event.event.ts + ', a slice named ' + 1073 event.event.name + ' with id=' + event.event.id + 1074 ' had a step before the start event.' 1075 }); 1076 continue; 1077 } 1078 1079 if (events[j].event.ph === 'F') { 1080 this.model_.importWarning({ 1081 type: 'async_slice_parse_error', 1082 message: 'At ' + event.event.ts + ', a slice named ' + 1083 event.event.name + ' with id=' + event.event.id + 1084 ' had a step after the finish event.' 1085 }); 1086 continue; 1087 } 1088 1089 var startIndex = j + (stepType === 'T' ? 0 : -1); 1090 var endIndex = startIndex + 1; 1091 1092 var subName = events[j].event.name; 1093 if (!events[j].event.argsStripped && 1094 (events[j].event.ph === 'T' || events[j].event.ph === 'p')) 1095 subName = subName + ':' + events[j].event.args.step; 1096 1097 var asyncSliceConstructor = 1098 tr.model.AsyncSlice.getConstructor( 1099 events[0].event.cat, 1100 subName); 1101 var subSlice = new asyncSliceConstructor( 1102 events[0].event.cat, 1103 subName, 1104 getEventColor(event, subName + j), 1105 timestampFromUs(events[startIndex].event.ts), 1106 this.deepCopyIfNeeded_(events[j].event.args), 1107 timestampFromUs( 1108 events[endIndex].event.ts - events[startIndex].event.ts), 1109 undefined, undefined, 1110 events[startIndex].event.argsStripped); 1111 subSlice.startThread = events[startIndex].thread; 1112 subSlice.endThread = events[endIndex].thread; 1113 subSlice.id = id; 1114 1115 slice.subSlices.push(subSlice); 1116 } 1117 1118 if (isValid) { 1119 // Add |slice| to the start-thread's asyncSlices. 1120 slice.startThread.asyncSliceGroup.push(slice); 1121 } 1122 1123 delete asyncEventStatesByNameThenID[name][id]; 1124 } 1125 } 1126 } 1127 }, 1128 1129 assertStepTypeMatches_: function(stepType, event) { 1130 if (stepType != event.event.ph) { 1131 this.model_.importWarning({ 1132 type: 'async_slice_parse_error', 1133 message: 'At ' + event.event.ts + ', a slice named ' + 1134 event.event.name + ' with id=' + event.event.id + 1135 ' had both begin and end steps, which is not allowed.' 1136 }); 1137 return false; 1138 } 1139 return true; 1140 }, 1141 1142 createFlowSlices_: function() { 1143 if (this.allFlowEvents_.length === 0) 1144 return; 1145 1146 var that = this; 1147 1148 function validateFlowEvent() { 1149 if (event.name === undefined) { 1150 that.model_.importWarning({ 1151 type: 'flow_slice_parse_error', 1152 message: 'Flow events (ph: s, t or f) require a name parameter.' 1153 }); 1154 return false; 1155 } 1156 1157 if (event.id === undefined) { 1158 that.model_.importWarning({ 1159 type: 'flow_slice_parse_error', 1160 message: 'Flow events (ph: s, t or f) require an id parameter.' 1161 }); 1162 return false; 1163 } 1164 return true; 1165 } 1166 1167 function createFlowEvent(thread, event, refGuid) { 1168 var ts = timestampFromUs(event.ts); 1169 var startSlice = thread.sliceGroup.findSliceAtTs(ts); 1170 if (startSlice === undefined) 1171 return undefined; 1172 1173 var flowEvent = new tr.model.FlowEvent( 1174 event.cat, 1175 event.id, 1176 event.name, 1177 getEventColor(event), 1178 timestampFromUs(event.ts), 1179 that.deepCopyIfNeeded_(event.args)); 1180 flowEvent.startSlice = startSlice; 1181 startSlice.outFlowEvents.push(flowEvent); 1182 return flowEvent; 1183 } 1184 1185 function finishFlowEventWith(flowEvent, thread, event, 1186 refGuid, bindToParent) { 1187 // TODO(nduca): Figure out endSlice from ts and binding point. 1188 var endSlice; 1189 var ts = timestampFromUs(event.ts); 1190 if (bindToParent) { 1191 endSlice = thread.sliceGroup.findSliceAtTs(ts); 1192 } else { 1193 endSlice = thread.sliceGroup.findNextSliceAfter(ts, refGuid); 1194 } 1195 if (endSlice === undefined) 1196 return false; 1197 endSlice.inFlowEvents.push(flowEvent); 1198 1199 // Modify the flowEvent with the new data. 1200 flowEvent.endSlice = endSlice; 1201 flowEvent.duration = ts - flowEvent.start; 1202 that.mergeArgsInto_(flowEvent.args, event.args, flowEvent.title); 1203 return true; 1204 } 1205 1206 // Actual import. 1207 this.allFlowEvents_.sort(function(x, y) { 1208 var d = x.event.ts - y.event.ts; 1209 if (d != 0) 1210 return d; 1211 return x.sequenceNumber - y.sequenceNumber; 1212 }); 1213 1214 var flowIdToEvent = {}; 1215 for (var i = 0; i < this.allFlowEvents_.length; ++i) { 1216 var data = this.allFlowEvents_[i]; 1217 var refGuid = data.refGuid; 1218 var event = data.event; 1219 var thread = data.thread; 1220 if (!validateFlowEvent(event)) 1221 continue; 1222 1223 var flowEvent; 1224 if (event.ph === 's') { 1225 if (flowIdToEvent[event.id]) { 1226 this.model_.importWarning({ 1227 type: 'flow_slice_start_error', 1228 message: 'event id ' + event.id + ' already seen when ' + 1229 'encountering start of flow event.'}); 1230 continue; 1231 } 1232 flowEvent = createFlowEvent(thread, event, refGuid); 1233 if (!flowEvent) { 1234 this.model_.importWarning({ 1235 type: 'flow_slice_start_error', 1236 message: 'event id ' + event.id + ' does not start ' + 1237 'at an actual slice, so cannot be created.'}); 1238 continue; 1239 } 1240 flowIdToEvent[event.id] = flowEvent; 1241 1242 } else if (event.ph === 't' || event.ph === 'f') { 1243 flowEvent = flowIdToEvent[event.id]; 1244 if (flowEvent === undefined) { 1245 this.model_.importWarning({ 1246 type: 'flow_slice_ordering_error', 1247 message: 'Found flow phase ' + event.ph + ' for id: ' + event.id + 1248 ' but no flow start found.' 1249 }); 1250 continue; 1251 } 1252 1253 var bindToParent = event.ph === 't'; 1254 1255 if (event.ph === 'f') { 1256 if (event.bp === undefined) { 1257 // Hack: input latency flow events used to be issued 1258 // without bp='e' letter in old traces. But, we still want their 1259 // flow events to parse "properly." This adjusts their binding 1260 // point so that they parse. 1261 // 1262 // TODO(nduca): Removal of this hack is tracked in 1263 // https://github.com/google/trace-viewer/issues/991. 1264 if (event.cat.indexOf('input') > -1) 1265 bindToParent = true; 1266 // Hack: ipc flow events used to be issued 1267 // without bp='e' letter in old traces. But, we still want their 1268 // flow events to parse "properly." This adjusts their binding 1269 // point so that they parse. 1270 // 1271 // TODO: Removal of this hack is tracked in 1272 // https://github.com/google/trace-viewer/issues/991. 1273 else if (event.cat.indexOf('ipc.flow') > -1) 1274 bindToParent = true; 1275 } else { 1276 if (event.bp !== 'e') { 1277 this.model_.importWarning({ 1278 type: 'flow_slice_bind_point_error', 1279 message: 'Flow event with invalid binding point (event.bp).' 1280 }); 1281 continue; 1282 } 1283 bindToParent = true; 1284 } 1285 } 1286 1287 var ok = finishFlowEventWith(flowEvent, thread, event, 1288 refGuid, bindToParent); 1289 if (ok) { 1290 that.model_.flowEvents.push(flowEvent); 1291 } else { 1292 this.model_.importWarning({ 1293 type: 'flow_slice_start_error', 1294 message: 'event id ' + event.id + ' does not end ' + 1295 'at an actual slice, so cannot be created.'}); 1296 } 1297 1298 flowIdToEvent[event.id] = undefined; 1299 1300 // If this is a step, then create another flow event. 1301 if (ok && event.ph === 't') { 1302 flowEvent = createFlowEvent(thread, event); 1303 flowIdToEvent[event.id] = flowEvent; 1304 } 1305 } 1306 } 1307 }, 1308 1309 /** 1310 * This function creates objects described via the N, D, and O phase 1311 * events. 1312 */ 1313 createExplicitObjects_: function() { 1314 if (this.allObjectEvents_.length == 0) 1315 return; 1316 1317 function processEvent(objectEventState) { 1318 var event = objectEventState.event; 1319 var thread = objectEventState.thread; 1320 if (event.name === undefined) { 1321 this.model_.importWarning({ 1322 type: 'object_parse_error', 1323 message: 'While processing ' + JSON.stringify(event) + ': ' + 1324 'Object events require an name parameter.' 1325 }); 1326 } 1327 1328 if (event.id === undefined) { 1329 this.model_.importWarning({ 1330 type: 'object_parse_error', 1331 message: 'While processing ' + JSON.stringify(event) + ': ' + 1332 'Object events require an id parameter.' 1333 }); 1334 } 1335 var process = thread.parent; 1336 var ts = timestampFromUs(event.ts); 1337 var instance; 1338 if (event.ph == 'N') { 1339 try { 1340 instance = process.objects.idWasCreated( 1341 event.id, event.cat, event.name, ts); 1342 } catch (e) { 1343 this.model_.importWarning({ 1344 type: 'object_parse_error', 1345 message: 'While processing create of ' + 1346 event.id + ' at ts=' + ts + ': ' + e 1347 }); 1348 return; 1349 } 1350 } else if (event.ph == 'O') { 1351 if (event.args.snapshot === undefined) { 1352 this.model_.importWarning({ 1353 type: 'object_parse_error', 1354 message: 'While processing ' + event.id + ' at ts=' + ts + ': ' + 1355 'Snapshots must have args: {snapshot: ...}' 1356 }); 1357 return; 1358 } 1359 var snapshot; 1360 try { 1361 var args = this.deepCopyIfNeeded_(event.args.snapshot); 1362 var cat; 1363 if (args.cat) { 1364 cat = args.cat; 1365 delete args.cat; 1366 } else { 1367 cat = event.cat; 1368 } 1369 1370 var baseTypename; 1371 if (args.base_type) { 1372 baseTypename = args.base_type; 1373 delete args.base_type; 1374 } else { 1375 baseTypename = undefined; 1376 } 1377 snapshot = process.objects.addSnapshot( 1378 event.id, cat, event.name, ts, 1379 args, baseTypename); 1380 snapshot.snapshottedOnThread = thread; 1381 } catch (e) { 1382 this.model_.importWarning({ 1383 type: 'object_parse_error', 1384 message: 'While processing snapshot of ' + 1385 event.id + ' at ts=' + ts + ': ' + e 1386 }); 1387 return; 1388 } 1389 instance = snapshot.objectInstance; 1390 } else if (event.ph == 'D') { 1391 try { 1392 process.objects.idWasDeleted(event.id, event.cat, event.name, ts); 1393 var instanceMap = process.objects.getOrCreateInstanceMap_(event.id); 1394 instance = instanceMap.lastInstance; 1395 } catch (e) { 1396 this.model_.importWarning({ 1397 type: 'object_parse_error', 1398 message: 'While processing delete of ' + 1399 event.id + ' at ts=' + ts + ': ' + e 1400 }); 1401 return; 1402 } 1403 } 1404 1405 if (instance) 1406 instance.colorId = getEventColor(event, instance.typeName); 1407 } 1408 1409 this.allObjectEvents_.sort(function(x, y) { 1410 var d = x.event.ts - y.event.ts; 1411 if (d != 0) 1412 return d; 1413 return x.sequenceNumber - y.sequenceNumber; 1414 }); 1415 1416 var allObjectEvents = this.allObjectEvents_; 1417 for (var i = 0; i < allObjectEvents.length; i++) { 1418 var objectEventState = allObjectEvents[i]; 1419 try { 1420 processEvent.call(this, objectEventState); 1421 } catch (e) { 1422 this.model_.importWarning({ 1423 type: 'object_parse_error', 1424 message: e.message 1425 }); 1426 } 1427 } 1428 }, 1429 1430 createImplicitObjects_: function() { 1431 tr.b.iterItems(this.model_.processes, function(pid, process) { 1432 this.createImplicitObjectsForProcess_(process); 1433 }, this); 1434 }, 1435 1436 // Here, we collect all the snapshots that internally contain a 1437 // Javascript-level object inside their args list that has an "id" field, 1438 // and turn that into a snapshot of the instance referred to by id. 1439 createImplicitObjectsForProcess_: function(process) { 1440 1441 function processField(referencingObject, 1442 referencingObjectFieldName, 1443 referencingObjectFieldValue, 1444 containingSnapshot) { 1445 if (!referencingObjectFieldValue) 1446 return; 1447 1448 if (referencingObjectFieldValue instanceof 1449 tr.model.ObjectSnapshot) 1450 return null; 1451 if (referencingObjectFieldValue.id === undefined) 1452 return; 1453 1454 var implicitSnapshot = referencingObjectFieldValue; 1455 1456 var rawId = implicitSnapshot.id; 1457 var m = /(.+)\/(.+)/.exec(rawId); 1458 if (!m) 1459 throw new Error('Implicit snapshots must have names.'); 1460 delete implicitSnapshot.id; 1461 var name = m[1]; 1462 var id = m[2]; 1463 var res; 1464 1465 var cat; 1466 if (implicitSnapshot.cat !== undefined) 1467 cat = implicitSnapshot.cat; 1468 else 1469 cat = containingSnapshot.objectInstance.category; 1470 1471 var baseTypename; 1472 if (implicitSnapshot.base_type) 1473 baseTypename = implicitSnapshot.base_type; 1474 else 1475 baseTypename = undefined; 1476 1477 try { 1478 res = process.objects.addSnapshot( 1479 id, cat, 1480 name, containingSnapshot.ts, 1481 implicitSnapshot, baseTypename); 1482 } catch (e) { 1483 this.model_.importWarning({ 1484 type: 'object_snapshot_parse_error', 1485 message: 'While processing implicit snapshot of ' + 1486 rawId + ' at ts=' + containingSnapshot.ts + ': ' + e 1487 }); 1488 return; 1489 } 1490 res.objectInstance.hasImplicitSnapshots = true; 1491 res.containingSnapshot = containingSnapshot; 1492 res.snapshottedOnThread = containingSnapshot.snapshottedOnThread; 1493 referencingObject[referencingObjectFieldName] = res; 1494 if (!(res instanceof tr.model.ObjectSnapshot)) 1495 throw new Error('Created object must be instanceof snapshot'); 1496 return res.args; 1497 } 1498 1499 /** 1500 * Iterates over the fields in the object, calling func for every 1501 * field/value found. 1502 * 1503 * @return {object} If the function does not want the field's value to be 1504 * iterated, return null. If iteration of the field value is desired, then 1505 * return either undefined (if the field value did not change) or the new 1506 * field value if it was changed. 1507 */ 1508 function iterObject(object, func, containingSnapshot, thisArg) { 1509 if (!(object instanceof Object)) 1510 return; 1511 1512 if (object instanceof Array) { 1513 for (var i = 0; i < object.length; i++) { 1514 var res = func.call(thisArg, object, i, object[i], 1515 containingSnapshot); 1516 if (res === null) 1517 continue; 1518 if (res) 1519 iterObject(res, func, containingSnapshot, thisArg); 1520 else 1521 iterObject(object[i], func, containingSnapshot, thisArg); 1522 } 1523 return; 1524 } 1525 1526 for (var key in object) { 1527 var res = func.call(thisArg, object, key, object[key], 1528 containingSnapshot); 1529 if (res === null) 1530 continue; 1531 if (res) 1532 iterObject(res, func, containingSnapshot, thisArg); 1533 else 1534 iterObject(object[key], func, containingSnapshot, thisArg); 1535 } 1536 } 1537 1538 // TODO(nduca): We may need to iterate the instances in sorted order by 1539 // creationTs. 1540 process.objects.iterObjectInstances(function(instance) { 1541 instance.snapshots.forEach(function(snapshot) { 1542 if (snapshot.args.id !== undefined) 1543 throw new Error('args cannot have an id field inside it'); 1544 iterObject(snapshot.args, processField, snapshot, this); 1545 }, this); 1546 }, this); 1547 }, 1548 1549 createMemoryDumps_: function() { 1550 tr.b.iterItems(this.allMemoryDumpEvents_, function(id, events) { 1551 // Calculate the range of the global memory dump. 1552 var range = new tr.b.Range(); 1553 if (events.global !== undefined) 1554 range.addValue(timestampFromUs(events.global.ts)); 1555 for (var i = 0; i < events.process.length; i++) 1556 range.addValue(timestampFromUs(events.process[i].ts)); 1557 1558 // Create the global memory dump. 1559 var globalMemoryDump = new tr.model.GlobalMemoryDump( 1560 this.model_, range.min); 1561 globalMemoryDump.duration = range.range; 1562 this.model_.globalMemoryDumps.push(globalMemoryDump); 1563 1564 // Create individual process memory dumps. 1565 if (events.process.length === 0) { 1566 this.model_.importWarning({ 1567 type: 'memory_dump_parse_error', 1568 message: 'No process memory dumps associated with global memory' + 1569 ' dump ' + id + '.' 1570 }); 1571 } 1572 1573 var allMemoryAllocatorDumpsByGuid = {}; 1574 var globalMemoryAllocatorDumpsByFullName = {}; 1575 1576 events.process.forEach(function(processEvent) { 1577 var pid = processEvent.pid; 1578 if (pid in globalMemoryDump.processMemoryDumps) { 1579 this.model_.importWarning({ 1580 type: 'memory_dump_parse_error', 1581 message: 'Multiple process memory dumps with pid=' + pid + 1582 ' for dump id ' + id + '.' 1583 }); 1584 return; 1585 } 1586 1587 var dumps = processEvent.args.dumps; 1588 if (dumps === undefined) { 1589 this.model_.importWarning({ 1590 type: 'memory_dump_parse_error', 1591 message: 'dumps not found in process memory dump for ' + 1592 'pid=' + pid + ' and dump id=' + id + '.' 1593 }); 1594 return; 1595 } 1596 1597 var process = this.model_.getOrCreateProcess(pid); 1598 var processMemoryDump = new tr.model.ProcessMemoryDump( 1599 globalMemoryDump, process, 1600 timestampFromUs(processEvent.ts)); 1601 1602 // Parse the totals, which are mandatory. 1603 if (dumps.process_totals === undefined || 1604 dumps.process_totals.resident_set_bytes === undefined) { 1605 this.model_.importWarning({ 1606 type: 'memory_dump_parse_error', 1607 message: 'Mandatory field resident_set_bytes not found in' + 1608 ' process memory dump for pid=' + pid + 1609 ' and dump id=' + id + '.' 1610 }); 1611 processMemoryDump.totalResidentBytes = undefined; 1612 } else { 1613 processMemoryDump.totalResidentBytes = parseInt( 1614 dumps.process_totals.resident_set_bytes, 16); 1615 } 1616 1617 // Populate the vmRegions, if present. 1618 if (dumps.process_mmaps && dumps.process_mmaps.vm_regions) { 1619 function parseByteStat(rawValue) { 1620 if (rawValue === undefined) 1621 return undefined; 1622 return parseInt(rawValue, 16); 1623 } 1624 1625 processMemoryDump.vmRegions = dumps.process_mmaps.vm_regions.map( 1626 function(rawRegion) { 1627 // See //base/trace_event/process_memory_maps.cc in Chromium. 1628 var byteStats = new tr.model.VMRegionByteStats( 1629 parseByteStat(rawRegion.bs.pc), 1630 parseByteStat(rawRegion.bs.pd), 1631 parseByteStat(rawRegion.bs.sc), 1632 parseByteStat(rawRegion.bs.sd), 1633 parseByteStat(rawRegion.bs.pss), 1634 parseByteStat(rawRegion.bs.sw) 1635 ); 1636 return new tr.model.VMRegion( 1637 parseInt(rawRegion.sa, 16), // startAddress 1638 parseInt(rawRegion.sz, 16), // sizeInBytes 1639 rawRegion.pf, // protectionFlags 1640 rawRegion.mf, // mappedFile 1641 byteStats 1642 ); 1643 } 1644 ); 1645 } 1646 1647 // Gather the process and global memory allocator dumps, if present. 1648 var processMemoryAllocatorDumpsByFullName = {}; 1649 if (dumps.allocators !== undefined) { 1650 // Construct the MemoryAllocatorDump objects without parent links 1651 // and add them to the processMemoryAllocatorDumpsByName and 1652 // globalMemoryAllocatorDumpsByName indices appropriately. 1653 tr.b.iterItems(dumps.allocators, 1654 function(fullName, rawAllocatorDump) { 1655 // Every memory allocator dump should have a GUID. If not, then 1656 // it cannot be associated with any edges. 1657 var guid = rawAllocatorDump.guid; 1658 if (guid === undefined) { 1659 this.model_.importWarning({ 1660 type: 'memory_dump_parse_error', 1661 message: 'Memory allocator dump ' + fullName + 1662 ' from pid=' + pid + ' does not have a GUID.' 1663 }); 1664 } 1665 1666 // Determine if this is a global memory allocator dump (check if 1667 // it's prefixed with 'global/'). 1668 var GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX = 'global/'; 1669 var containerMemoryDump; 1670 var dstIndex; 1671 if (fullName.startsWith(GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX)) { 1672 // Global memory allocator dump. 1673 fullName = fullName.substring( 1674 GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX.length); 1675 containerMemoryDump = globalMemoryDump; 1676 dstIndex = globalMemoryAllocatorDumpsByFullName; 1677 } else { 1678 // Process memory allocator dump. 1679 containerMemoryDump = processMemoryDump; 1680 dstIndex = processMemoryAllocatorDumpsByFullName; 1681 } 1682 1683 // Construct or retrieve a memory allocator dump with the provided 1684 // GUID. 1685 var allocatorDump = allMemoryAllocatorDumpsByGuid[guid]; 1686 if (allocatorDump === undefined) { 1687 if (fullName in dstIndex) { 1688 this.model_.importWarning({ 1689 type: 'memory_dump_parse_error', 1690 message: 'Multiple GUIDs provided for' + 1691 ' memory allocator dump ' + fullName + ': ' + 1692 dstIndex[fullName].guid + ', ' + guid + ' (ignored).' 1693 }); 1694 return; 1695 } 1696 allocatorDump = new tr.model.MemoryAllocatorDump( 1697 containerMemoryDump, fullName, guid); 1698 dstIndex[fullName] = allocatorDump; 1699 if (guid !== undefined) 1700 allMemoryAllocatorDumpsByGuid[guid] = allocatorDump; 1701 } else { 1702 // A memory allocator dump with this GUID has already been 1703 // dumped (so we will only add new attributes). Check that it 1704 // belonged to the same process or was also global. 1705 if (allocatorDump.containerMemoryDump !== containerMemoryDump) { 1706 this.model_.importWarning({ 1707 type: 'memory_dump_parse_error', 1708 message: 'Memory allocator dump ' + fullName + 1709 ' (GUID=' + guid + ') dumped in different contexts.' 1710 }); 1711 return; 1712 } 1713 // Check that the names of the memory allocator dumps match. 1714 if (allocatorDump.fullName !== fullName) { 1715 this.model_.importWarning({ 1716 type: 'memory_dump_parse_error', 1717 message: 'Memory allocator dump with GUID=' + guid + 1718 ' has multiple names: ' + allocatorDump.fullName + 1719 ', ' + fullName + ' (ignored).' 1720 }); 1721 return; 1722 } 1723 } 1724 1725 // Add all new attributes to the memory allocator dump. 1726 var attributes = rawAllocatorDump.attrs; 1727 tr.b.iterItems(attributes, function(attrName, attrArgs) { 1728 if (attrName in allocatorDump.attributes) { 1729 // Skip existing attributes of the memory allocator dump. 1730 this.model_.importWarning({ 1731 type: 'memory_dump_parse_error', 1732 message: 'Multiple values provided for attribute ' + 1733 attrName + ' of memory allocator dump ' + fullName + 1734 ' (GUID=' + guid + ').' 1735 }); 1736 return; 1737 } 1738 var attrValue = 1739 tr.model.Attribute.fromDictIfPossible(attrArgs); 1740 allocatorDump.addAttribute(attrName, attrValue); 1741 }, this); 1742 }, this); 1743 } 1744 1745 // Find the root allocator dumps and establish the parent links of 1746 // the process memory dump. 1747 processMemoryDump.memoryAllocatorDumps = 1748 this.inferMemoryAllocatorDumpTree_( 1749 processMemoryAllocatorDumpsByFullName); 1750 1751 process.memoryDumps.push(processMemoryDump); 1752 globalMemoryDump.processMemoryDumps[pid] = processMemoryDump; 1753 }, this); 1754 1755 // Find the root allocator dumps and establish the parent links of 1756 // the global memory dump. 1757 globalMemoryDump.memoryAllocatorDumps = 1758 this.inferMemoryAllocatorDumpTree_( 1759 globalMemoryAllocatorDumpsByFullName); 1760 1761 // Set up edges between memory allocator dumps. 1762 events.process.forEach(function(processEvent) { 1763 var dumps = processEvent.args.dumps; 1764 if (dumps === undefined) 1765 return; 1766 1767 var edges = dumps.allocators_graph; 1768 if (edges === undefined) 1769 return; 1770 1771 edges.forEach(function(rawEdge) { 1772 var sourceGuid = rawEdge.source; 1773 var sourceDump = allMemoryAllocatorDumpsByGuid[sourceGuid]; 1774 if (sourceDump === undefined) { 1775 this.model_.importWarning({ 1776 type: 'memory_dump_parse_error', 1777 message: 'Edge is missing source memory allocator dump (GUID=' + 1778 sourceGuid + ')' 1779 }); 1780 return; 1781 } 1782 1783 var targetGuid = rawEdge.target; 1784 var targetDump = allMemoryAllocatorDumpsByGuid[targetGuid]; 1785 if (targetDump === undefined) { 1786 this.model_.importWarning({ 1787 type: 'memory_dump_parse_error', 1788 message: 'Edge is missing target memory allocator dump (GUID=' + 1789 targetGuid + ')' 1790 }); 1791 return; 1792 } 1793 1794 var importance = rawEdge.importance; 1795 var edge = new tr.model.MemoryAllocatorDumpLink( 1796 sourceDump, targetDump, importance); 1797 1798 switch (rawEdge.type) { 1799 case 'ownership': 1800 if (sourceDump.owns !== undefined) { 1801 this.model_.importWarning({ 1802 type: 'memory_dump_parse_error', 1803 message: 'Memory allocator dump ' + sourceDump.fullName + 1804 ' (GUID=' + sourceGuid + ') already owns a memory' + 1805 ' allocator dump (' + 1806 sourceDump.owns.target.fullName + ').' 1807 }); 1808 return; 1809 } 1810 sourceDump.owns = edge; 1811 targetDump.ownedBy.push(edge); 1812 break; 1813 1814 case 'retention': 1815 sourceDump.retains.push(edge); 1816 targetDump.retainedBy.push(edge); 1817 break; 1818 1819 default: 1820 this.model_.importWarning({ 1821 type: 'memory_dump_parse_error', 1822 message: 'Invalid edge type: ' + rawEdge.type + 1823 ' (source=' + sourceGuid + ', target=' + targetGuid + 1824 ', importance=' + importance + ').' 1825 }); 1826 } 1827 }, this); 1828 }, this); 1829 }, this); 1830 }, 1831 1832 inferMemoryAllocatorDumpTree_: function(memoryAllocatorDumpsByFullName) { 1833 var rootAllocatorDumps = []; 1834 1835 var fullNames = Object.keys(memoryAllocatorDumpsByFullName); 1836 fullNames.sort(); 1837 fullNames.forEach(function(fullName) { 1838 var allocatorDump = memoryAllocatorDumpsByFullName[fullName]; 1839 1840 // This is a loop because we might need to build implicit 1841 // ancestors in case they were not present in the trace. 1842 while (true) { 1843 var lastSlashIndex = fullName.lastIndexOf('/'); 1844 if (lastSlashIndex === -1) { 1845 // If the dump is a root, add it to the top-level 1846 // rootAllocatorDumps list. 1847 rootAllocatorDumps.push(allocatorDump); 1848 break; 1849 } 1850 1851 // If the dump is not a root, find its parent. 1852 var parentFullName = fullName.substring(0, lastSlashIndex); 1853 var parentAllocatorDump = 1854 memoryAllocatorDumpsByFullName[parentFullName]; 1855 1856 // If the parent dump does not exist yet, we build an implicit 1857 // one and continue up the ancestor chain. 1858 var parentAlreadyExisted = true; 1859 if (parentAllocatorDump === undefined) { 1860 parentAlreadyExisted = false; 1861 parentAllocatorDump = new tr.model.MemoryAllocatorDump( 1862 allocatorDump.containerMemoryDump, parentFullName); 1863 memoryAllocatorDumpsByFullName[parentFullName] = 1864 parentAllocatorDump; 1865 } 1866 1867 // Setup the parent <-> children relationships 1868 allocatorDump.parent = parentAllocatorDump; 1869 parentAllocatorDump.children.push(allocatorDump); 1870 1871 // If the parent already existed, then its ancestors were/will be 1872 // constructed in another iteration of the forEach loop. 1873 if (parentAlreadyExisted) 1874 break; 1875 1876 fullName = parentFullName; 1877 allocatorDump = parentAllocatorDump; 1878 } 1879 }, this); 1880 1881 return rootAllocatorDumps; 1882 }, 1883 1884 joinObjectRefs_: function() { 1885 tr.b.iterItems(this.model_.processes, function(pid, process) { 1886 this.joinObjectRefsForProcess_(process); 1887 }, this); 1888 }, 1889 1890 joinObjectRefsForProcess_: function(process) { 1891 // Iterate the world, looking for id_refs 1892 var patchupsToApply = []; 1893 tr.b.iterItems(process.threads, function(tid, thread) { 1894 thread.asyncSliceGroup.slices.forEach(function(item) { 1895 this.searchItemForIDRefs_( 1896 patchupsToApply, process.objects, 'start', item); 1897 }, this); 1898 thread.sliceGroup.slices.forEach(function(item) { 1899 this.searchItemForIDRefs_( 1900 patchupsToApply, process.objects, 'start', item); 1901 }, this); 1902 }, this); 1903 process.objects.iterObjectInstances(function(instance) { 1904 instance.snapshots.forEach(function(item) { 1905 this.searchItemForIDRefs_( 1906 patchupsToApply, process.objects, 'ts', item); 1907 }, this); 1908 }, this); 1909 1910 // Change all the fields pointing at id_refs to their real values. 1911 patchupsToApply.forEach(function(patchup) { 1912 patchup.object[patchup.field] = patchup.value; 1913 }); 1914 }, 1915 1916 searchItemForIDRefs_: function(patchupsToApply, objectCollection, 1917 itemTimestampField, item) { 1918 if (!item.args) 1919 throw new Error('item is missing its args'); 1920 1921 function handleField(object, fieldName, fieldValue) { 1922 if (!fieldValue || (!fieldValue.id_ref && !fieldValue.idRef)) 1923 return; 1924 1925 var id = fieldValue.id_ref || fieldValue.idRef; 1926 var ts = item[itemTimestampField]; 1927 var snapshot = objectCollection.getSnapshotAt(id, ts); 1928 if (!snapshot) 1929 return; 1930 1931 // We have to delay the actual change to the new value until after all 1932 // refs have been located. Otherwise, we could end up recursing in 1933 // ways we definitely didn't intend. 1934 patchupsToApply.push({object: object, 1935 field: fieldName, 1936 value: snapshot}); 1937 } 1938 function iterObjectFieldsRecursively(object) { 1939 if (!(object instanceof Object)) 1940 return; 1941 1942 if ((object instanceof tr.model.ObjectSnapshot) || 1943 (object instanceof Float32Array) || 1944 (object instanceof tr.b.Quad)) 1945 return; 1946 1947 if (object instanceof Array) { 1948 for (var i = 0; i < object.length; i++) { 1949 handleField(object, i, object[i]); 1950 iterObjectFieldsRecursively(object[i]); 1951 } 1952 return; 1953 } 1954 1955 for (var key in object) { 1956 var value = object[key]; 1957 handleField(object, key, value); 1958 iterObjectFieldsRecursively(value); 1959 } 1960 } 1961 1962 iterObjectFieldsRecursively(item.args); 1963 } 1964 }; 1965 1966 tr.importer.Importer.register(TraceEventImporter); 1967 1968 return { 1969 TraceEventImporter: TraceEventImporter 1970 }; 1971}); 1972</script> 1973