1 // Copyright 2014 The Chromium 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 /** 6 * @constructor 7 * @param {!WebInspector.TracingManager} tracingManager 8 * @param {!WebInspector.TracingModel} tracingModel 9 * @param {!WebInspector.TimelineModel.Filter} recordFilter 10 * @extends {WebInspector.TimelineModel} 11 */ 12 WebInspector.TracingTimelineModel = function(tracingManager, tracingModel, recordFilter) 13 { 14 WebInspector.TimelineModel.call(this); 15 16 this._tracingManager = tracingManager; 17 this._tracingModel = tracingModel; 18 this._recordFilter = recordFilter; 19 this._tracingManager.addEventListener(WebInspector.TracingManager.Events.TracingStarted, this._onTracingStarted, this); 20 this._tracingManager.addEventListener(WebInspector.TracingManager.Events.EventsCollected, this._onEventsCollected, this); 21 this._tracingManager.addEventListener(WebInspector.TracingManager.Events.TracingComplete, this._onTracingComplete, this); 22 this.reset(); 23 } 24 25 WebInspector.TracingTimelineModel.RecordType = { 26 Program: "Program", 27 EventDispatch: "EventDispatch", 28 29 GPUTask: "GPUTask", 30 31 RequestMainThreadFrame: "RequestMainThreadFrame", 32 BeginFrame: "BeginFrame", 33 BeginMainThreadFrame: "BeginMainThreadFrame", 34 ActivateLayerTree: "ActivateLayerTree", 35 DrawFrame: "DrawFrame", 36 ScheduleStyleRecalculation: "ScheduleStyleRecalculation", 37 RecalculateStyles: "RecalculateStyles", 38 InvalidateLayout: "InvalidateLayout", 39 Layout: "Layout", 40 UpdateLayer: "UpdateLayer", 41 UpdateLayerTree: "UpdateLayerTree", 42 PaintSetup: "PaintSetup", 43 Paint: "Paint", 44 PaintImage: "PaintImage", 45 Rasterize: "Rasterize", 46 RasterTask: "RasterTask", 47 ScrollLayer: "ScrollLayer", 48 CompositeLayers: "CompositeLayers", 49 50 ParseHTML: "ParseHTML", 51 52 TimerInstall: "TimerInstall", 53 TimerRemove: "TimerRemove", 54 TimerFire: "TimerFire", 55 56 XHRReadyStateChange: "XHRReadyStateChange", 57 XHRLoad: "XHRLoad", 58 EvaluateScript: "EvaluateScript", 59 60 MarkLoad: "MarkLoad", 61 MarkDOMContent: "MarkDOMContent", 62 MarkFirstPaint: "MarkFirstPaint", 63 64 TimeStamp: "TimeStamp", 65 ConsoleTime: "ConsoleTime", 66 67 ResourceSendRequest: "ResourceSendRequest", 68 ResourceReceiveResponse: "ResourceReceiveResponse", 69 ResourceReceivedData: "ResourceReceivedData", 70 ResourceFinish: "ResourceFinish", 71 72 FunctionCall: "FunctionCall", 73 GCEvent: "GCEvent", 74 JSFrame: "JSFrame", 75 JSSample: "JSSample", 76 77 UpdateCounters: "UpdateCounters", 78 79 RequestAnimationFrame: "RequestAnimationFrame", 80 CancelAnimationFrame: "CancelAnimationFrame", 81 FireAnimationFrame: "FireAnimationFrame", 82 83 WebSocketCreate : "WebSocketCreate", 84 WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest", 85 WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse", 86 WebSocketDestroy : "WebSocketDestroy", 87 88 EmbedderCallback : "EmbedderCallback", 89 90 CallStack: "CallStack", 91 SetLayerTreeId: "SetLayerTreeId", 92 TracingStartedInPage: "TracingStartedInPage", 93 TracingSessionIdForWorker: "TracingSessionIdForWorker", 94 95 DecodeImage: "Decode Image", 96 ResizeImage: "Resize Image", 97 DrawLazyPixelRef: "Draw LazyPixelRef", 98 DecodeLazyPixelRef: "Decode LazyPixelRef", 99 100 LazyPixelRef: "LazyPixelRef", 101 LayerTreeHostImplSnapshot: "cc::LayerTreeHostImpl", 102 PictureSnapshot: "cc::Picture" 103 }; 104 105 /** 106 * @constructor 107 * @param {string} name 108 */ 109 WebInspector.TracingTimelineModel.VirtualThread = function(name) 110 { 111 this.name = name; 112 /** @type {!Array.<!WebInspector.TracingModel.Event>} */ 113 this.events = []; 114 /** @type {!Array.<!Array.<!WebInspector.TracingModel.Event>>} */ 115 this.asyncEvents = []; 116 } 117 118 WebInspector.TracingTimelineModel.prototype = { 119 /** 120 * @param {boolean} captureStacks 121 * @param {boolean} captureMemory 122 * @param {boolean} capturePictures 123 */ 124 startRecording: function(captureStacks, captureMemory, capturePictures) 125 { 126 function disabledByDefault(category) 127 { 128 return "disabled-by-default-" + category; 129 } 130 var categoriesArray = [ 131 "-*", 132 disabledByDefault("devtools.timeline"), 133 disabledByDefault("devtools.timeline.frame"), 134 WebInspector.TracingModel.ConsoleEventCategory 135 ]; 136 if (captureStacks) { 137 categoriesArray.push(disabledByDefault("devtools.timeline.stack")); 138 if (Runtime.experiments.isEnabled("timelineJSCPUProfile")) { 139 this._jsProfilerStarted = true; 140 this._currentTarget = WebInspector.context.flavor(WebInspector.Target); 141 this._configureCpuProfilerSamplingInterval(); 142 this._currentTarget.profilerAgent().start(); 143 } 144 } 145 if (capturePictures) { 146 categoriesArray = categoriesArray.concat([ 147 disabledByDefault("devtools.timeline.layers"), 148 disabledByDefault("devtools.timeline.picture"), 149 disabledByDefault("blink.graphics_context_annotations")]); 150 } 151 var categories = categoriesArray.join(","); 152 this._startRecordingWithCategories(categories); 153 }, 154 155 stopRecording: function() 156 { 157 this._stopCallbackBarrier = new CallbackBarrier(); 158 if (this._jsProfilerStarted) { 159 this._currentTarget.profilerAgent().stop(this._stopCallbackBarrier.createCallback(this._didStopRecordingJSSamples.bind(this))); 160 this._jsProfilerStarted = false; 161 } 162 this._tracingManager.stop(); 163 }, 164 165 /** 166 * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events 167 */ 168 setEventsForTest: function(events) 169 { 170 this._onTracingStarted(); 171 this._tracingModel.addEvents(events); 172 this._onTracingComplete(); 173 }, 174 175 _configureCpuProfilerSamplingInterval: function() 176 { 177 var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000; 178 this._currentTarget.profilerAgent().setSamplingInterval(intervalUs, didChangeInterval); 179 180 function didChangeInterval(error) 181 { 182 if (error) 183 WebInspector.console.error(error); 184 } 185 }, 186 187 /** 188 * @param {string} categories 189 */ 190 _startRecordingWithCategories: function(categories) 191 { 192 this._tracingManager.start(categories, ""); 193 }, 194 195 _onTracingStarted: function() 196 { 197 this.reset(); 198 this._tracingModel.reset(); 199 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted); 200 }, 201 202 /** 203 * @param {!WebInspector.Event} event 204 */ 205 _onEventsCollected: function(event) 206 { 207 var traceEvents = /** @type {!Array.<!WebInspector.TracingManager.EventPayload>} */ (event.data); 208 this._tracingModel.addEvents(traceEvents); 209 }, 210 211 _onTracingComplete: function() 212 { 213 this._tracingModel.tracingComplete(); 214 if (this._stopCallbackBarrier) 215 this._stopCallbackBarrier.callWhenDone(this._didStopRecordingTraceEvents.bind(this)); 216 else 217 this._didStopRecordingTraceEvents(); 218 }, 219 220 /** 221 * @param {?Protocol.Error} error 222 * @param {?ProfilerAgent.CPUProfile} cpuProfile 223 */ 224 _didStopRecordingJSSamples: function(error, cpuProfile) 225 { 226 if (error) 227 WebInspector.console.error(error); 228 this._cpuProfile = cpuProfile; 229 }, 230 231 _didStopRecordingTraceEvents: function() 232 { 233 this._stopCallbackBarrier = null; 234 var events = this._tracingModel.devtoolsPageMetadataEvents(); 235 var workerMetadataEvents = this._tracingModel.devtoolsWorkerMetadataEvents(); 236 237 this._resetProcessingState(); 238 for (var i = 0, length = events.length; i < length; i++) { 239 var event = events[i]; 240 var process = event.thread.process(); 241 var startTime = event.startTime; 242 243 var endTime = Infinity; 244 if (i + 1 < length) 245 endTime = events[i + 1].startTime; 246 247 var threads = process.sortedThreads(); 248 for (var j = 0; j < threads.length; j++) { 249 var thread = threads[j]; 250 if (thread.name() === "WebCore: Worker" && !workerMetadataEvents.some(function(e) { return e.args["data"]["workerThreadId"] === thread.id(); })) 251 continue; 252 this._processThreadEvents(startTime, endTime, event.thread, thread); 253 } 254 } 255 this._resetProcessingState(); 256 257 this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime); 258 259 if (this._cpuProfile) { 260 var jsSamples = WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(this, this._cpuProfile); 261 this._inspectedTargetEvents = this._inspectedTargetEvents.mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime); 262 this._setMainThreadEvents(this.mainThreadEvents().mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime)); 263 this._cpuProfile = null; 264 } 265 266 this._buildTimelineRecords(); 267 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped); 268 }, 269 270 /** 271 * @return {number} 272 */ 273 minimumRecordTime: function() 274 { 275 return this._tracingModel.minimumRecordTime(); 276 }, 277 278 /** 279 * @return {number} 280 */ 281 maximumRecordTime: function() 282 { 283 return this._tracingModel.maximumRecordTime(); 284 }, 285 286 /** 287 * @return {!Array.<!WebInspector.TracingModel.Event>} 288 */ 289 inspectedTargetEvents: function() 290 { 291 return this._inspectedTargetEvents; 292 }, 293 294 /** 295 * @return {!Array.<!WebInspector.TracingModel.Event>} 296 */ 297 mainThreadEvents: function() 298 { 299 return this._mainThreadEvents; 300 }, 301 302 /** 303 * @param {!Array.<!WebInspector.TracingModel.Event>} events 304 */ 305 _setMainThreadEvents: function(events) 306 { 307 this._mainThreadEvents = events; 308 }, 309 310 /** 311 * @return {!Array.<!Array.<!WebInspector.TracingModel.Event>>} 312 */ 313 mainThreadAsyncEvents: function() 314 { 315 return this._mainThreadAsyncEvents; 316 }, 317 318 /** 319 * @return {!Array.<!WebInspector.TracingTimelineModel.VirtualThread>} 320 */ 321 virtualThreads: function() 322 { 323 return this._virtualThreads; 324 }, 325 326 /** 327 * @param {!WebInspector.ChunkedFileReader} fileReader 328 * @param {!WebInspector.Progress} progress 329 * @return {!WebInspector.OutputStream} 330 */ 331 createLoader: function(fileReader, progress) 332 { 333 return new WebInspector.TracingModelLoader(this, fileReader, progress); 334 }, 335 336 /** 337 * @param {!WebInspector.OutputStream} stream 338 */ 339 writeToStream: function(stream) 340 { 341 var saver = new WebInspector.TracingTimelineSaver(stream); 342 this._tracingModel.writeToStream(stream, saver); 343 }, 344 345 reset: function() 346 { 347 this._virtualThreads = []; 348 this._mainThreadEvents = []; 349 this._mainThreadAsyncEvents = []; 350 this._inspectedTargetEvents = []; 351 WebInspector.TimelineModel.prototype.reset.call(this); 352 }, 353 354 _buildTimelineRecords: function() 355 { 356 var recordStack = []; 357 var mainThreadEvents = this.mainThreadEvents(); 358 359 /** 360 * @param {!WebInspector.TracingTimelineModel.TraceEventRecord} record 361 */ 362 function copyChildrenToParent(record) 363 { 364 var parent = record.parent; 365 var parentChildren = parent.children(); 366 var children = record.children(); 367 for (var j = 0; j < children.length; ++j) 368 children[j].parent = parent; 369 parentChildren.splice.apply(parentChildren, [parentChildren.indexOf(record), 1].concat(children)); 370 } 371 372 for (var i = 0, size = mainThreadEvents.length; i < size; ++i) { 373 var event = mainThreadEvents[i]; 374 while (recordStack.length) { 375 var top = recordStack.peekLast(); 376 // When we've got a not-yet-complete async event at the top of the stack, 377 // see if we can close it by a matching end event. If this doesn't happen 378 // before end of top-level event (presumably, a "Program"), pretend the 379 // async event never happened. 380 if (!top._event.endTime) { 381 if (event.phase !== WebInspector.TracingModel.Phase.AsyncEnd && recordStack[0]._event.endTime >= event.startTime) 382 break; 383 if (event.phase === WebInspector.TracingModel.Phase.AsyncEnd) { 384 if (top._event.name === event.name) { 385 top.setEndTime(event.startTime); 386 recordStack.pop(); 387 } 388 break; 389 } 390 // Delete incomplete async record from parent and adopt its children. 391 recordStack.pop(); 392 copyChildrenToParent(top); 393 continue; 394 } else if (top._event.endTime >= event.startTime) { 395 break; 396 } 397 recordStack.pop(); 398 if (!recordStack.length) 399 this._addTopLevelRecord(top); 400 } 401 if (event.phase === WebInspector.TracingModel.Phase.AsyncEnd) 402 continue; 403 var record = new WebInspector.TracingTimelineModel.TraceEventRecord(this, event); 404 if (WebInspector.TracingTimelineUIUtils.isMarkerEvent(event)) 405 this._eventDividerRecords.push(record); 406 if (!this._recordFilter.accept(record)) 407 continue; 408 var parentRecord = recordStack.peekLast(); 409 if (parentRecord) 410 parentRecord._addChild(record); 411 if (event.endTime || (event.phase === WebInspector.TracingModel.Phase.AsyncBegin && parentRecord)) 412 recordStack.push(record); 413 } 414 415 // Close all remaining incomplete async events. 416 while (recordStack.length > 1) { 417 var top = recordStack.pop(); 418 if (!top._event.endTime) { 419 // Delete incomplete async record from parent and adopt its children. 420 copyChildrenToParent(top); 421 } 422 } 423 424 if (recordStack.length) 425 this._addTopLevelRecord(recordStack[0]); 426 }, 427 428 /** 429 * @param {!WebInspector.TracingTimelineModel.TraceEventRecord} record 430 */ 431 _addTopLevelRecord: function(record) 432 { 433 this._updateBoundaries(record); 434 this._records.push(record); 435 if (record.type() === WebInspector.TracingTimelineModel.RecordType.Program) 436 this._mainThreadTasks.push(record); 437 if (record.type() === WebInspector.TracingTimelineModel.RecordType.GPUTask) 438 this._gpuThreadTasks.push(record); 439 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record); 440 }, 441 442 _resetProcessingState: function() 443 { 444 this._sendRequestEvents = {}; 445 this._timerEvents = {}; 446 this._requestAnimationFrameEvents = {}; 447 this._layoutInvalidate = {}; 448 this._lastScheduleStyleRecalculation = {}; 449 this._webSocketCreateEvents = {}; 450 this._paintImageEventByPixelRefId = {}; 451 this._lastPaintForLayer = {}; 452 this._lastRecalculateStylesEvent = null; 453 this._currentScriptEvent = null; 454 this._eventStack = []; 455 }, 456 457 /** 458 * @param {number} startTime 459 * @param {?number} endTime 460 * @param {!WebInspector.TracingModel.Thread} mainThread 461 * @param {!WebInspector.TracingModel.Thread} thread 462 */ 463 _processThreadEvents: function(startTime, endTime, mainThread, thread) 464 { 465 var events = thread.events(); 466 var length = events.length; 467 var i = events.lowerBound(startTime, function (time, event) { return time - event.startTime }); 468 469 var threadEvents; 470 if (thread === mainThread) { 471 threadEvents = this._mainThreadEvents; 472 this._mainThreadAsyncEvents = this._mainThreadAsyncEvents.concat(thread.asyncEvents()); 473 } else { 474 var virtualThread = new WebInspector.TracingTimelineModel.VirtualThread(thread.name()); 475 threadEvents = virtualThread.events; 476 virtualThread.asyncEvents = virtualThread.asyncEvents.concat(thread.asyncEvents()); 477 this._virtualThreads.push(virtualThread); 478 } 479 480 this._eventStack = []; 481 for (; i < length; i++) { 482 var event = events[i]; 483 if (endTime && event.startTime >= endTime) 484 break; 485 this._processEvent(event); 486 threadEvents.push(event); 487 this._inspectedTargetEvents.push(event); 488 } 489 }, 490 491 /** 492 * @param {!WebInspector.TracingModel.Event} event 493 */ 494 _processEvent: function(event) 495 { 496 var recordTypes = WebInspector.TracingTimelineModel.RecordType; 497 498 var eventStack = this._eventStack; 499 while (eventStack.length && eventStack.peekLast().endTime < event.startTime) 500 eventStack.pop(); 501 var duration = event.duration; 502 if (duration) { 503 if (eventStack.length) { 504 var parent = eventStack.peekLast(); 505 parent.selfTime -= duration; 506 } 507 event.selfTime = duration; 508 eventStack.push(event); 509 } 510 511 if (this._currentScriptEvent && event.startTime > this._currentScriptEvent.endTime) 512 this._currentScriptEvent = null; 513 514 switch (event.name) { 515 case recordTypes.CallStack: 516 var lastMainThreadEvent = this.mainThreadEvents().peekLast(); 517 if (lastMainThreadEvent && event.args["stack"] && event.args["stack"].length) 518 lastMainThreadEvent.stackTrace = event.args["stack"]; 519 break; 520 521 case recordTypes.ResourceSendRequest: 522 this._sendRequestEvents[event.args["data"]["requestId"]] = event; 523 event.imageURL = event.args["data"]["url"]; 524 break; 525 526 case recordTypes.ResourceReceiveResponse: 527 case recordTypes.ResourceReceivedData: 528 case recordTypes.ResourceFinish: 529 event.initiator = this._sendRequestEvents[event.args["data"]["requestId"]]; 530 if (event.initiator) 531 event.imageURL = event.initiator.imageURL; 532 break; 533 534 case recordTypes.TimerInstall: 535 this._timerEvents[event.args["data"]["timerId"]] = event; 536 break; 537 538 case recordTypes.TimerFire: 539 event.initiator = this._timerEvents[event.args["data"]["timerId"]]; 540 break; 541 542 case recordTypes.RequestAnimationFrame: 543 this._requestAnimationFrameEvents[event.args["data"]["id"]] = event; 544 break; 545 546 case recordTypes.FireAnimationFrame: 547 event.initiator = this._requestAnimationFrameEvents[event.args["data"]["id"]]; 548 break; 549 550 case recordTypes.ScheduleStyleRecalculation: 551 this._lastScheduleStyleRecalculation[event.args["frame"]] = event; 552 break; 553 554 case recordTypes.RecalculateStyles: 555 event.initiator = this._lastScheduleStyleRecalculation[event.args["frame"]]; 556 this._lastRecalculateStylesEvent = event; 557 break; 558 559 case recordTypes.InvalidateLayout: 560 // Consider style recalculation as a reason for layout invalidation, 561 // but only if we had no earlier layout invalidation records. 562 var layoutInitator = event; 563 var frameId = event.args["frame"]; 564 if (!this._layoutInvalidate[frameId] && this._lastRecalculateStylesEvent && this._lastRecalculateStylesEvent.endTime > event.startTime) 565 layoutInitator = this._lastRecalculateStylesEvent.initiator; 566 this._layoutInvalidate[frameId] = layoutInitator; 567 break; 568 569 case recordTypes.Layout: 570 var frameId = event.args["beginData"]["frame"]; 571 event.initiator = this._layoutInvalidate[frameId]; 572 event.backendNodeId = event.args["endData"]["rootNode"]; 573 event.highlightQuad = event.args["endData"]["root"]; 574 this._layoutInvalidate[frameId] = null; 575 if (this._currentScriptEvent) 576 event.warning = WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."); 577 break; 578 579 case recordTypes.WebSocketCreate: 580 this._webSocketCreateEvents[event.args["data"]["identifier"]] = event; 581 break; 582 583 case recordTypes.WebSocketSendHandshakeRequest: 584 case recordTypes.WebSocketReceiveHandshakeResponse: 585 case recordTypes.WebSocketDestroy: 586 event.initiator = this._webSocketCreateEvents[event.args["data"]["identifier"]]; 587 break; 588 589 case recordTypes.EvaluateScript: 590 case recordTypes.FunctionCall: 591 if (!this._currentScriptEvent) 592 this._currentScriptEvent = event; 593 break; 594 595 case recordTypes.SetLayerTreeId: 596 this._inspectedTargetLayerTreeId = event.args["layerTreeId"]; 597 break; 598 599 case recordTypes.Paint: 600 event.highlightQuad = event.args["data"]["clip"]; 601 event.backendNodeId = event.args["data"]["nodeId"]; 602 var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer); 603 if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId) 604 break; 605 // Only keep layer paint events, skip paints for subframes that get painted to the same layer as parent. 606 if (!event.args["data"]["layerId"]) 607 break; 608 this._lastPaintForLayer[layerUpdateEvent.args["layerId"]] = event; 609 break; 610 611 case recordTypes.PictureSnapshot: 612 var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer); 613 if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId) 614 break; 615 var paintEvent = this._lastPaintForLayer[layerUpdateEvent.args["layerId"]]; 616 if (paintEvent) 617 paintEvent.picture = event; 618 break; 619 620 case recordTypes.ScrollLayer: 621 event.backendNodeId = event.args["data"]["nodeId"]; 622 break; 623 624 case recordTypes.PaintImage: 625 event.backendNodeId = event.args["data"]["nodeId"]; 626 event.imageURL = event.args["data"]["url"]; 627 break; 628 629 case recordTypes.DecodeImage: 630 case recordTypes.ResizeImage: 631 var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage); 632 if (!paintImageEvent) { 633 var decodeLazyPixelRefEvent = this._findAncestorEvent(recordTypes.DecodeLazyPixelRef); 634 paintImageEvent = decodeLazyPixelRefEvent && this._paintImageEventByPixelRefId[decodeLazyPixelRefEvent.args["LazyPixelRef"]]; 635 } 636 if (!paintImageEvent) 637 break; 638 event.backendNodeId = paintImageEvent.backendNodeId; 639 event.imageURL = paintImageEvent.imageURL; 640 break; 641 642 case recordTypes.DrawLazyPixelRef: 643 var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage); 644 if (!paintImageEvent) 645 break; 646 this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]] = paintImageEvent; 647 event.backendNodeId = paintImageEvent.backendNodeId; 648 event.imageURL = paintImageEvent.imageURL; 649 break; 650 } 651 }, 652 653 /** 654 * @param {string} name 655 * @return {?WebInspector.TracingModel.Event} 656 */ 657 _findAncestorEvent: function(name) 658 { 659 for (var i = this._eventStack.length - 1; i >= 0; --i) { 660 var event = this._eventStack[i]; 661 if (event.name === name) 662 return event; 663 } 664 return null; 665 }, 666 667 __proto__: WebInspector.TimelineModel.prototype 668 } 669 670 /** 671 * @interface 672 */ 673 WebInspector.TracingTimelineModel.Filter = function() { } 674 675 WebInspector.TracingTimelineModel.Filter.prototype = { 676 /** 677 * @param {!WebInspector.TracingModel.Event} event 678 * @return {boolean} 679 */ 680 accept: function(event) { } 681 } 682 683 /** 684 * @constructor 685 * @implements {WebInspector.TracingTimelineModel.Filter} 686 * @param {!Array.<string>} eventNames 687 */ 688 WebInspector.TracingTimelineModel.EventNameFilter = function(eventNames) 689 { 690 this._eventNames = eventNames.keySet(); 691 } 692 693 WebInspector.TracingTimelineModel.EventNameFilter.prototype = { 694 /** 695 * @param {!WebInspector.TracingModel.Event} event 696 * @return {boolean} 697 */ 698 accept: function(event) 699 { 700 throw new Error("Not implemented."); 701 } 702 } 703 704 /** 705 * @constructor 706 * @extends {WebInspector.TracingTimelineModel.EventNameFilter} 707 * @param {!Array.<string>} includeNames 708 */ 709 WebInspector.TracingTimelineModel.InclusiveEventNameFilter = function(includeNames) 710 { 711 WebInspector.TracingTimelineModel.EventNameFilter.call(this, includeNames) 712 } 713 714 WebInspector.TracingTimelineModel.InclusiveEventNameFilter.prototype = { 715 /** 716 * @override 717 * @param {!WebInspector.TracingModel.Event} event 718 * @return {boolean} 719 */ 720 accept: function(event) 721 { 722 return event.category === WebInspector.TracingModel.ConsoleEventCategory || !!this._eventNames[event.name]; 723 }, 724 __proto__: WebInspector.TracingTimelineModel.EventNameFilter.prototype 725 } 726 727 /** 728 * @constructor 729 * @extends {WebInspector.TracingTimelineModel.EventNameFilter} 730 * @param {!Array.<string>} excludeNames 731 */ 732 WebInspector.TracingTimelineModel.ExclusiveEventNameFilter = function(excludeNames) 733 { 734 WebInspector.TracingTimelineModel.EventNameFilter.call(this, excludeNames) 735 } 736 737 WebInspector.TracingTimelineModel.ExclusiveEventNameFilter.prototype = { 738 /** 739 * @override 740 * @param {!WebInspector.TracingModel.Event} event 741 * @return {boolean} 742 */ 743 accept: function(event) 744 { 745 return !this._eventNames[event.name]; 746 }, 747 __proto__: WebInspector.TracingTimelineModel.EventNameFilter.prototype 748 } 749 750 /** 751 * @constructor 752 * @implements {WebInspector.TimelineModel.Record} 753 * @param {!WebInspector.TimelineModel} model 754 * @param {!WebInspector.TracingModel.Event} traceEvent 755 */ 756 WebInspector.TracingTimelineModel.TraceEventRecord = function(model, traceEvent) 757 { 758 this._model = model; 759 this._event = traceEvent; 760 traceEvent._timelineRecord = this; 761 this._children = []; 762 } 763 764 WebInspector.TracingTimelineModel.TraceEventRecord.prototype = { 765 /** 766 * @return {?Array.<!ConsoleAgent.CallFrame>} 767 */ 768 callSiteStackTrace: function() 769 { 770 var initiator = this._event.initiator; 771 return initiator ? initiator.stackTrace : null; 772 }, 773 774 /** 775 * @return {?WebInspector.TimelineModel.Record} 776 */ 777 initiator: function() 778 { 779 var initiator = this._event.initiator; 780 return initiator ? initiator._timelineRecord : null; 781 }, 782 783 /** 784 * @return {?WebInspector.Target} 785 */ 786 target: function() 787 { 788 return this._event.thread.target(); 789 }, 790 791 /** 792 * @return {number} 793 */ 794 selfTime: function() 795 { 796 return this._event.selfTime; 797 }, 798 799 /** 800 * @return {!Array.<!WebInspector.TimelineModel.Record>} 801 */ 802 children: function() 803 { 804 return this._children; 805 }, 806 807 /** 808 * @return {number} 809 */ 810 startTime: function() 811 { 812 return this._event.startTime; 813 }, 814 815 /** 816 * @return {string} 817 */ 818 thread: function() 819 { 820 // FIXME: Should return the actual thread name. 821 return WebInspector.TimelineModel.MainThreadName; 822 }, 823 824 /** 825 * @return {number} 826 */ 827 endTime: function() 828 { 829 return this._endTime || this._event.endTime || this._event.startTime; 830 }, 831 832 /** 833 * @param {number} endTime 834 */ 835 setEndTime: function(endTime) 836 { 837 this._endTime = endTime; 838 }, 839 840 /** 841 * @return {!Object} 842 */ 843 data: function() 844 { 845 return this._event.args["data"]; 846 }, 847 848 /** 849 * @return {string} 850 */ 851 type: function() 852 { 853 if (this._event.category === WebInspector.TracingModel.ConsoleEventCategory) 854 return WebInspector.TracingTimelineModel.RecordType.ConsoleTime; 855 return this._event.name; 856 }, 857 858 /** 859 * @return {string} 860 */ 861 frameId: function() 862 { 863 switch (this._event.name) { 864 case WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation: 865 case WebInspector.TracingTimelineModel.RecordType.RecalculateStyles: 866 case WebInspector.TracingTimelineModel.RecordType.InvalidateLayout: 867 return this._event.args["frameId"]; 868 case WebInspector.TracingTimelineModel.RecordType.Layout: 869 return this._event.args["beginData"]["frameId"]; 870 default: 871 var data = this._event.args["data"]; 872 return (data && data["frame"]) || ""; 873 } 874 }, 875 876 /** 877 * @return {?Array.<!ConsoleAgent.CallFrame>} 878 */ 879 stackTrace: function() 880 { 881 return this._event.stackTrace; 882 }, 883 884 /** 885 * @param {string} key 886 * @return {?Object} 887 */ 888 getUserObject: function(key) 889 { 890 if (key === "TimelineUIUtils::preview-element") 891 return this._event.previewElement; 892 throw new Error("Unexpected key: " + key); 893 }, 894 895 /** 896 * @param {string} key 897 * @param {?Object|undefined} value 898 */ 899 setUserObject: function(key, value) 900 { 901 if (key !== "TimelineUIUtils::preview-element") 902 throw new Error("Unexpected key: " + key); 903 this._event.previewElement = /** @type {?Element} */ (value); 904 }, 905 906 /** 907 * @return {?Array.<string>} 908 */ 909 warnings: function() 910 { 911 if (this._event.warning) 912 return [this._event.warning]; 913 return null; 914 }, 915 916 /** 917 * @return {!WebInspector.TracingModel.Event} 918 */ 919 traceEvent: function() 920 { 921 return this._event; 922 }, 923 924 /** 925 * @param {!WebInspector.TracingTimelineModel.TraceEventRecord} child 926 */ 927 _addChild: function(child) 928 { 929 this._children.push(child); 930 child.parent = this; 931 }, 932 933 /** 934 * @return {!WebInspector.TimelineModel} 935 */ 936 timelineModel: function() 937 { 938 return this._model; 939 } 940 } 941 942 943 944 /** 945 * @constructor 946 * @implements {WebInspector.OutputStream} 947 * @param {!WebInspector.TracingTimelineModel} model 948 * @param {!{cancel: function()}} reader 949 * @param {!WebInspector.Progress} progress 950 */ 951 WebInspector.TracingModelLoader = function(model, reader, progress) 952 { 953 this._model = model; 954 this._reader = reader; 955 this._progress = progress; 956 this._buffer = ""; 957 this._firstChunk = true; 958 this._loader = new WebInspector.TracingModel.Loader(model._tracingModel); 959 } 960 961 WebInspector.TracingModelLoader.prototype = { 962 /** 963 * @param {string} chunk 964 */ 965 write: function(chunk) 966 { 967 var data = this._buffer + chunk; 968 var lastIndex = 0; 969 var index; 970 do { 971 index = lastIndex; 972 lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index); 973 } while (lastIndex !== -1) 974 975 var json = data.slice(0, index) + "]"; 976 this._buffer = data.slice(index); 977 978 if (!index) 979 return; 980 981 if (this._firstChunk) { 982 this._model._onTracingStarted(); 983 } else { 984 var commaIndex = json.indexOf(","); 985 if (commaIndex !== -1) 986 json = json.slice(commaIndex + 1); 987 json = "[" + json; 988 } 989 990 var items; 991 try { 992 items = /** @type {!Array.<!WebInspector.TracingManager.EventPayload>} */ (JSON.parse(json)); 993 } catch (e) { 994 this._reportErrorAndCancelLoading("Malformed timeline data: " + e); 995 return; 996 } 997 998 if (this._firstChunk) { 999 this._firstChunk = false; 1000 if (this._looksLikeAppVersion(items[0])) { 1001 this._reportErrorAndCancelLoading("Old Timeline format is not supported."); 1002 return; 1003 } 1004 } 1005 1006 try { 1007 this._loader.loadNextChunk(items); 1008 } catch(e) { 1009 this._reportErrorAndCancelLoading("Malformed timeline data: " + e); 1010 return; 1011 } 1012 }, 1013 1014 _reportErrorAndCancelLoading: function(messsage) 1015 { 1016 WebInspector.console.error(messsage); 1017 this._model._onTracingComplete(); 1018 this._model.reset(); 1019 this._reader.cancel(); 1020 this._progress.done(); 1021 }, 1022 1023 _looksLikeAppVersion: function(item) 1024 { 1025 return typeof item === "string" && item.indexOf("Chrome") !== -1; 1026 }, 1027 1028 close: function() 1029 { 1030 this._loader.finish(); 1031 this._model._onTracingComplete(); 1032 } 1033 } 1034 1035 /** 1036 * @constructor 1037 * @param {!WebInspector.OutputStream} stream 1038 * @implements {WebInspector.OutputStreamDelegate} 1039 */ 1040 WebInspector.TracingTimelineSaver = function(stream) 1041 { 1042 this._stream = stream; 1043 } 1044 1045 WebInspector.TracingTimelineSaver.prototype = { 1046 onTransferStarted: function() 1047 { 1048 this._stream.write("["); 1049 }, 1050 1051 onTransferFinished: function() 1052 { 1053 this._stream.write("]"); 1054 }, 1055 1056 /** 1057 * @param {!WebInspector.ChunkedReader} reader 1058 */ 1059 onChunkTransferred: function(reader) { }, 1060 1061 /** 1062 * @param {!WebInspector.ChunkedReader} reader 1063 * @param {!Event} event 1064 */ 1065 onError: function(reader, event) { }, 1066 } 1067