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 * @param {!WebInspector.TracingModel} tracingModel 7 * @constructor 8 * @extends {WebInspector.TimelineModel} 9 */ 10 WebInspector.TracingTimelineModel = function(tracingModel) 11 { 12 WebInspector.TimelineModel.call(this, tracingModel.target()); 13 this._tracingModel = tracingModel; 14 this._mainThreadEvents = []; 15 this._inspectedTargetEvents = []; 16 17 this.reset(); 18 } 19 20 WebInspector.TracingTimelineModel.RecordType = { 21 Program: "Program", 22 EventDispatch: "EventDispatch", 23 24 GPUTask: "GPUTask", 25 26 RequestMainThreadFrame: "RequestMainThreadFrame", 27 BeginFrame: "BeginFrame", 28 BeginMainThreadFrame: "BeginMainThreadFrame", 29 ActivateLayerTree: "ActivateLayerTree", 30 DrawFrame: "DrawFrame", 31 ScheduleStyleRecalculation: "ScheduleStyleRecalculation", 32 RecalculateStyles: "RecalculateStyles", 33 InvalidateLayout: "InvalidateLayout", 34 Layout: "Layout", 35 UpdateLayer: "UpdateLayer", 36 PaintSetup: "PaintSetup", 37 Paint: "Paint", 38 PaintImage: "PaintImage", 39 Rasterize: "Rasterize", 40 RasterTask: "RasterTask", 41 ScrollLayer: "ScrollLayer", 42 CompositeLayers: "CompositeLayers", 43 44 ParseHTML: "ParseHTML", 45 46 TimerInstall: "TimerInstall", 47 TimerRemove: "TimerRemove", 48 TimerFire: "TimerFire", 49 50 XHRReadyStateChange: "XHRReadyStateChange", 51 XHRLoad: "XHRLoad", 52 EvaluateScript: "EvaluateScript", 53 54 MarkLoad: "MarkLoad", 55 MarkDOMContent: "MarkDOMContent", 56 MarkFirstPaint: "MarkFirstPaint", 57 58 TimeStamp: "TimeStamp", 59 ConsoleTime: "ConsoleTime", 60 61 ResourceSendRequest: "ResourceSendRequest", 62 ResourceReceiveResponse: "ResourceReceiveResponse", 63 ResourceReceivedData: "ResourceReceivedData", 64 ResourceFinish: "ResourceFinish", 65 66 FunctionCall: "FunctionCall", 67 GCEvent: "GCEvent", 68 JSFrame: "JSFrame", 69 70 UpdateCounters: "UpdateCounters", 71 72 RequestAnimationFrame: "RequestAnimationFrame", 73 CancelAnimationFrame: "CancelAnimationFrame", 74 FireAnimationFrame: "FireAnimationFrame", 75 76 WebSocketCreate : "WebSocketCreate", 77 WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest", 78 WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse", 79 WebSocketDestroy : "WebSocketDestroy", 80 81 EmbedderCallback : "EmbedderCallback", 82 83 CallStack: "CallStack", 84 SetLayerTreeId: "SetLayerTreeId", 85 TracingStartedInPage: "TracingStartedInPage", 86 87 DecodeImage: "Decode Image", 88 ResizeImage: "Resize Image", 89 DrawLazyPixelRef: "Draw LazyPixelRef", 90 DecodeLazyPixelRef: "Decode LazyPixelRef", 91 92 LazyPixelRef: "LazyPixelRef", 93 LayerTreeHostImplSnapshot: "cc::LayerTreeHostImpl", 94 PictureSnapshot: "cc::Picture" 95 }; 96 97 WebInspector.TracingTimelineModel.defaultTracingCategoryFilter = "*,disabled-by-default-cc.debug,disabled-by-default-devtools.timeline,disabled-by-default-devtools.timeline.frame"; 98 99 WebInspector.TracingTimelineModel.prototype = { 100 /** 101 * @param {boolean} captureStacks 102 * @param {boolean} captureMemory 103 * @param {boolean} capturePictures 104 */ 105 startRecording: function(captureStacks, captureMemory, capturePictures) 106 { 107 var categories; 108 if (WebInspector.experimentsSettings.timelineTracingMode.isEnabled()) { 109 categories = WebInspector.TracingTimelineModel.defaultTracingCategoryFilter; 110 } else { 111 var categoriesArray = ["disabled-by-default-devtools.timeline", "disabled-by-default-devtools.timeline.frame", "devtools"]; 112 if (captureStacks) 113 categoriesArray.push("disabled-by-default-devtools.timeline.stack"); 114 if (capturePictures) 115 categoriesArray.push("disabled-by-default-devtools.timeline.layers", "disabled-by-default-devtools.timeline.picture"); 116 categories = categoriesArray.join(","); 117 } 118 this._startRecordingWithCategories(categories); 119 }, 120 121 stopRecording: function() 122 { 123 this._tracingModel.stop(this._didStopRecordingTraceEvents.bind(this)); 124 }, 125 126 /** 127 * @param {string} sessionId 128 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events 129 */ 130 setEventsForTest: function(sessionId, events) 131 { 132 this.reset(); 133 this._didStartRecordingTraceEvents(); 134 this._tracingModel.setEventsForTest(sessionId, events); 135 this._didStopRecordingTraceEvents(); 136 }, 137 138 /** 139 * @param {string} categories 140 */ 141 _startRecordingWithCategories: function(categories) 142 { 143 this.reset(); 144 this._tracingModel.start(categories, "", this._didStartRecordingTraceEvents.bind(this)); 145 }, 146 147 _didStartRecordingTraceEvents: function() 148 { 149 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted); 150 }, 151 152 _didStopRecordingTraceEvents: function() 153 { 154 var events = this._tracingModel.devtoolsMetadataEvents(); 155 events.sort(WebInspector.TracingModel.Event.compareStartTime); 156 157 this._resetProcessingState(); 158 for (var i = 0, length = events.length; i < length; i++) { 159 var event = events[i]; 160 var process = event.thread.process(); 161 var startTime = event.startTime; 162 163 var endTime = Infinity; 164 if (i + 1 < length) 165 endTime = events[i + 1].startTime; 166 167 process.sortedThreads().forEach(this._processThreadEvents.bind(this, startTime, endTime, event.thread)); 168 } 169 this._resetProcessingState(); 170 171 this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime); 172 173 this._buildTimelineRecords(); 174 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped); 175 }, 176 177 /** 178 * @return {number} 179 */ 180 minimumRecordTime: function() 181 { 182 return this._tracingModel.minimumRecordTime(); 183 }, 184 185 /** 186 * @return {number} 187 */ 188 maximumRecordTime: function() 189 { 190 return this._tracingModel.maximumRecordTime(); 191 }, 192 193 /** 194 * @return {!Array.<!WebInspector.TracingModel.Event>} 195 */ 196 inspectedTargetEvents: function() 197 { 198 return this._inspectedTargetEvents; 199 }, 200 201 /** 202 * @return {!Array.<!WebInspector.TracingModel.Event>} 203 */ 204 mainThreadEvents: function() 205 { 206 return this._mainThreadEvents; 207 }, 208 209 reset: function() 210 { 211 this._mainThreadEvents = []; 212 this._inspectedTargetEvents = []; 213 WebInspector.TimelineModel.prototype.reset.call(this); 214 }, 215 216 _buildTimelineRecords: function() 217 { 218 var recordStack = []; 219 var mainThreadEvents = this._mainThreadEvents; 220 for (var i = 0, size = mainThreadEvents.length; i < size; ++i) { 221 var event = mainThreadEvents[i]; 222 while (recordStack.length) { 223 var top = recordStack.peekLast(); 224 if (top._event.endTime >= event.startTime) 225 break; 226 recordStack.pop(); 227 } 228 var parentRecord = recordStack.peekLast() || null; 229 var record = new WebInspector.TracingTimelineModel.TraceEventRecord(this, event, parentRecord); 230 if (WebInspector.TracingTimelineUIUtils.isEventDivider(record)) 231 this._eventDividerRecords.push(record); 232 if (!recordStack.length) 233 this._addTopLevelRecord(record); 234 if (event.endTime) 235 recordStack.push(record); 236 } 237 }, 238 239 /** 240 * @param {!WebInspector.TracingTimelineModel.TraceEventRecord} record 241 */ 242 _addTopLevelRecord: function(record) 243 { 244 this._updateBoundaries(record); 245 this._records.push(record); 246 if (record.type() === WebInspector.TimelineModel.RecordType.Program) 247 this._mainThreadTasks.push(record); 248 if (record.type() === WebInspector.TimelineModel.RecordType.GPUTask) 249 this._gpuThreadTasks.push(record); 250 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record); 251 }, 252 253 _resetProcessingState: function() 254 { 255 this._sendRequestEvents = {}; 256 this._timerEvents = {}; 257 this._requestAnimationFrameEvents = {}; 258 this._layoutInvalidate = {}; 259 this._lastScheduleStyleRecalculation = {}; 260 this._webSocketCreateEvents = {}; 261 this._paintImageEventByPixelRefId = {}; 262 this._lastPaintForLayer = {}; 263 this._lastRecalculateStylesEvent = null; 264 this._currentScriptEvent = null; 265 this._eventStack = []; 266 }, 267 268 /** 269 * @param {number} startTime 270 * @param {?number} endTime 271 * @param {!WebInspector.TracingModel.Thread} mainThread 272 * @param {!WebInspector.TracingModel.Thread} thread 273 */ 274 _processThreadEvents: function(startTime, endTime, mainThread, thread) 275 { 276 var events = thread.events(); 277 var length = events.length; 278 var i = events.lowerBound(startTime, function (time, event) { return time - event.startTime }); 279 280 this._eventStack = []; 281 for (; i < length; i++) { 282 var event = events[i]; 283 if (endTime && event.startTime >= endTime) 284 break; 285 this._processEvent(event); 286 if (thread === mainThread) 287 this._mainThreadEvents.push(event); 288 this._inspectedTargetEvents.push(event); 289 } 290 }, 291 292 /** 293 * @param {!WebInspector.TracingModel.Event} event 294 */ 295 _processEvent: function(event) 296 { 297 var recordTypes = WebInspector.TracingTimelineModel.RecordType; 298 299 var eventStack = this._eventStack; 300 while (eventStack.length && eventStack.peekLast().endTime < event.startTime) 301 eventStack.pop(); 302 var duration = event.duration; 303 if (duration) { 304 if (eventStack.length) { 305 var parent = eventStack.peekLast(); 306 parent.selfTime -= duration; 307 } 308 event.selfTime = duration; 309 eventStack.push(event); 310 } 311 312 if (this._currentScriptEvent && event.startTime > this._currentScriptEvent.endTime) 313 this._currentScriptEvent = null; 314 315 switch (event.name) { 316 case recordTypes.CallStack: 317 var lastMainThreadEvent = this._mainThreadEvents.peekLast(); 318 if (lastMainThreadEvent && event.args.stack && event.args.stack.length) 319 lastMainThreadEvent.stackTrace = event.args.stack; 320 break; 321 322 case recordTypes.ResourceSendRequest: 323 this._sendRequestEvents[event.args.data["requestId"]] = event; 324 event.imageURL = event.args.data["url"]; 325 break; 326 327 case recordTypes.ResourceReceiveResponse: 328 case recordTypes.ResourceReceivedData: 329 case recordTypes.ResourceFinish: 330 event.initiator = this._sendRequestEvents[event.args.data["requestId"]]; 331 if (event.initiator) 332 event.imageURL = event.initiator.imageURL; 333 break; 334 335 case recordTypes.TimerInstall: 336 this._timerEvents[event.args.data["timerId"]] = event; 337 break; 338 339 case recordTypes.TimerFire: 340 event.initiator = this._timerEvents[event.args.data["timerId"]]; 341 break; 342 343 case recordTypes.RequestAnimationFrame: 344 this._requestAnimationFrameEvents[event.args.data["id"]] = event; 345 break; 346 347 case recordTypes.FireAnimationFrame: 348 event.initiator = this._requestAnimationFrameEvents[event.args.data["id"]]; 349 break; 350 351 case recordTypes.ScheduleStyleRecalculation: 352 this._lastScheduleStyleRecalculation[event.args.frame] = event; 353 break; 354 355 case recordTypes.RecalculateStyles: 356 event.initiator = this._lastScheduleStyleRecalculation[event.args.frame]; 357 this._lastRecalculateStylesEvent = event; 358 break; 359 360 case recordTypes.InvalidateLayout: 361 // Consider style recalculation as a reason for layout invalidation, 362 // but only if we had no earlier layout invalidation records. 363 var layoutInitator = event; 364 var frameId = event.args.frame; 365 if (!this._layoutInvalidate[frameId] && this._lastRecalculateStylesEvent && this._lastRecalculateStylesEvent.endTime > event.startTime) 366 layoutInitator = this._lastRecalculateStylesEvent.initiator; 367 this._layoutInvalidate[frameId] = layoutInitator; 368 break; 369 370 case recordTypes.Layout: 371 var frameId = event.args["beginData"]["frame"]; 372 event.initiator = this._layoutInvalidate[frameId]; 373 event.backendNodeId = event.args["endData"]["rootNode"]; 374 event.highlightQuad = event.args["endData"]["root"]; 375 this._layoutInvalidate[frameId] = null; 376 if (this._currentScriptEvent) 377 event.warning = WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."); 378 break; 379 380 case recordTypes.WebSocketCreate: 381 this._webSocketCreateEvents[event.args.data["identifier"]] = event; 382 break; 383 384 case recordTypes.WebSocketSendHandshakeRequest: 385 case recordTypes.WebSocketReceiveHandshakeResponse: 386 case recordTypes.WebSocketDestroy: 387 event.initiator = this._webSocketCreateEvents[event.args.data["identifier"]]; 388 break; 389 390 case recordTypes.EvaluateScript: 391 case recordTypes.FunctionCall: 392 if (!this._currentScriptEvent) 393 this._currentScriptEvent = event; 394 break; 395 396 case recordTypes.SetLayerTreeId: 397 this._inspectedTargetLayerTreeId = event.args["layerTreeId"]; 398 break; 399 400 case recordTypes.Paint: 401 event.highlightQuad = event.args["data"]["clip"]; 402 event.backendNodeId = event.args["data"]["nodeId"]; 403 var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer); 404 if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId) 405 break; 406 this._lastPaintForLayer[layerUpdateEvent.args["layerId"]] = event; 407 break; 408 409 case recordTypes.PictureSnapshot: 410 var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer); 411 if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId) 412 break; 413 var paintEvent = this._lastPaintForLayer[layerUpdateEvent.args["layerId"]]; 414 if (!paintEvent) 415 break; 416 paintEvent.picture = event.args["snapshot"]["skp64"]; 417 break; 418 419 case recordTypes.ScrollLayer: 420 event.backendNodeId = event.args["data"]["nodeId"]; 421 break; 422 423 case recordTypes.PaintImage: 424 event.backendNodeId = event.args["data"]["nodeId"]; 425 event.imageURL = event.args["data"]["url"]; 426 break; 427 428 case recordTypes.DecodeImage: 429 case recordTypes.ResizeImage: 430 var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage); 431 if (!paintImageEvent) { 432 var decodeLazyPixelRefEvent = this._findAncestorEvent(recordTypes.DecodeLazyPixelRef); 433 paintImageEvent = decodeLazyPixelRefEvent && this._paintImageEventByPixelRefId[decodeLazyPixelRefEvent.args["LazyPixelRef"]]; 434 } 435 if (!paintImageEvent) 436 break; 437 event.backendNodeId = paintImageEvent.backendNodeId; 438 event.imageURL = paintImageEvent.imageURL; 439 break; 440 441 case recordTypes.DrawLazyPixelRef: 442 var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage); 443 if (!paintImageEvent) 444 break; 445 this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]] = paintImageEvent; 446 event.backendNodeId = paintImageEvent.backendNodeId; 447 event.imageURL = paintImageEvent.imageURL; 448 break; 449 } 450 }, 451 452 /** 453 * @param {string} name 454 * @return {?WebInspector.TracingModel.Event} 455 */ 456 _findAncestorEvent: function(name) 457 { 458 for (var i = this._eventStack.length - 1; i >= 0; --i) { 459 var event = this._eventStack[i]; 460 if (event.name === name) 461 return event; 462 } 463 return null; 464 }, 465 466 __proto__: WebInspector.TimelineModel.prototype 467 } 468 469 /** 470 * @constructor 471 * @implements {WebInspector.TimelineModel.Record} 472 * @param {!WebInspector.TimelineModel} model 473 * @param {!WebInspector.TracingModel.Event} traceEvent 474 * @param {?WebInspector.TracingTimelineModel.TraceEventRecord} parentRecord 475 */ 476 WebInspector.TracingTimelineModel.TraceEventRecord = function(model, traceEvent, parentRecord) 477 { 478 this._model = model; 479 this._event = traceEvent; 480 traceEvent._timelineRecord = this; 481 if (parentRecord) { 482 this.parent = parentRecord; 483 parentRecord._children.push(this); 484 } 485 this._children = []; 486 } 487 488 WebInspector.TracingTimelineModel.TraceEventRecord.prototype = { 489 /** 490 * @return {?Array.<!ConsoleAgent.CallFrame>} 491 */ 492 callSiteStackTrace: function() 493 { 494 var initiator = this._event.initiator; 495 return initiator ? initiator.stackTrace : null; 496 }, 497 498 /** 499 * @return {?WebInspector.TimelineModel.Record} 500 */ 501 initiator: function() 502 { 503 var initiator = this._event.initiator; 504 return initiator ? initiator._timelineRecord : null; 505 }, 506 507 /** 508 * @return {!WebInspector.Target} 509 */ 510 target: function() 511 { 512 return this._model.target(); 513 }, 514 515 /** 516 * @return {number} 517 */ 518 selfTime: function() 519 { 520 return this._event.selfTime; 521 }, 522 523 /** 524 * @return {!Array.<!WebInspector.TimelineModel.Record>} 525 */ 526 children: function() 527 { 528 return this._children; 529 }, 530 531 /** 532 * @return {!WebInspector.TimelineCategory} 533 */ 534 category: function() 535 { 536 var style = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(this._event.name); 537 return style.category; 538 }, 539 540 /** 541 * @return {number} 542 */ 543 startTime: function() 544 { 545 return this._event.startTime; 546 }, 547 548 /** 549 * @return {string|undefined} 550 */ 551 thread: function() 552 { 553 return "CPU"; 554 }, 555 556 /** 557 * @return {number} 558 */ 559 endTime: function() 560 { 561 return this._event.endTime || this._event.startTime; 562 }, 563 564 /** 565 * @param {number} endTime 566 */ 567 setEndTime: function(endTime) 568 { 569 throw new Error("Unsupported operation setEndTime"); 570 }, 571 572 /** 573 * @return {!Object} 574 */ 575 data: function() 576 { 577 return this._event.args.data; 578 }, 579 580 /** 581 * @return {string} 582 */ 583 type: function() 584 { 585 return this._event.name; 586 }, 587 588 /** 589 * @return {string} 590 */ 591 frameId: function() 592 { 593 switch (this._event.name) { 594 case WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation: 595 case WebInspector.TracingTimelineModel.RecordType.RecalculateStyles: 596 case WebInspector.TracingTimelineModel.RecordType.InvalidateLayout: 597 return this._event.args["frameId"]; 598 case WebInspector.TracingTimelineModel.RecordType.Layout: 599 return this._event.args["beginData"]["frameId"]; 600 default: 601 var data = this._event.args.data; 602 return (data && data["frame"]) || ""; 603 } 604 }, 605 606 /** 607 * @return {?Array.<!ConsoleAgent.CallFrame>} 608 */ 609 stackTrace: function() 610 { 611 return this._event.stackTrace; 612 }, 613 614 /** 615 * @param {string} key 616 * @return {?Object} 617 */ 618 getUserObject: function(key) 619 { 620 if (key === "TimelineUIUtils::preview-element") 621 return this._event.previewElement; 622 throw new Error("Unexpected key: " + key); 623 }, 624 625 /** 626 * @param {string} key 627 * @param {?Object|undefined} value 628 */ 629 setUserObject: function(key, value) 630 { 631 if (key !== "TimelineUIUtils::preview-element") 632 throw new Error("Unexpected key: " + key); 633 this._event.previewElement = /** @type {?Element} */ (value); 634 }, 635 636 /** 637 * @return {!Object.<string, number>} 638 */ 639 aggregatedStats: function() 640 { 641 return {}; 642 }, 643 644 /** 645 * @return {?Array.<string>} 646 */ 647 warnings: function() 648 { 649 if (this._event.warning) 650 return [this._event.warning]; 651 return null; 652 }, 653 654 /** 655 * @return {!WebInspector.TracingModel.Event} 656 */ 657 traceEvent: function() 658 { 659 return this._event; 660 } 661 } 662