1 /* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 */ 34 WebInspector.TimelineFrameModelBase = function() 35 { 36 this.reset(); 37 } 38 39 WebInspector.TimelineFrameModelBase.prototype = { 40 /** 41 * @param {boolean} value 42 */ 43 setMergeRecords: function(value) 44 { 45 }, 46 47 /** 48 * @return {!Array.<!WebInspector.TimelineFrame>} 49 */ 50 frames: function() 51 { 52 return this._frames; 53 }, 54 55 /** 56 * @param {number} startTime 57 * @param {number} endTime 58 * @return {!Array.<!WebInspector.TimelineFrame>} 59 */ 60 filteredFrames: function(startTime, endTime) 61 { 62 /** 63 * @param {number} value 64 * @param {!WebInspector.TimelineFrame} object 65 * @return {number} 66 */ 67 function compareStartTime(value, object) 68 { 69 return value - object.startTime; 70 } 71 /** 72 * @param {number} value 73 * @param {!WebInspector.TimelineFrame} object 74 * @return {number} 75 */ 76 function compareEndTime(value, object) 77 { 78 return value - object.endTime; 79 } 80 var frames = this._frames; 81 var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, frames, compareEndTime); 82 var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, frames, compareStartTime); 83 return frames.slice(firstFrame, lastFrame); 84 }, 85 86 reset: function() 87 { 88 this._minimumRecordTime = Infinity; 89 this._frames = []; 90 this._lastFrame = null; 91 this._lastLayerTree = null; 92 this._hasThreadedCompositing = false; 93 this._mainFrameCommitted = false; 94 this._mainFrameRequested = false; 95 this._framePendingCommit = null; 96 }, 97 98 /** 99 * @param {number} startTime 100 */ 101 handleBeginFrame: function(startTime) 102 { 103 if (!this._lastFrame) 104 this._startBackgroundFrame(startTime); 105 }, 106 107 /** 108 * @param {number} startTime 109 */ 110 handleDrawFrame: function(startTime) 111 { 112 if (!this._lastFrame) { 113 this._startBackgroundFrame(startTime); 114 return; 115 } 116 117 // - if it wasn't drawn, it didn't happen! 118 // - only show frames that either did not wait for the main thread frame or had one committed. 119 if (this._mainFrameCommitted || !this._mainFrameRequested) 120 this._startBackgroundFrame(startTime); 121 this._mainFrameCommitted = false; 122 }, 123 124 handleActivateLayerTree: function() 125 { 126 if (!this._lastFrame) 127 return; 128 this._mainFrameRequested = false; 129 this._mainFrameCommitted = true; 130 if (this._framePendingActivation) { 131 this._lastFrame._addTimeForCategories(this._framePendingActivation.timeByCategory); 132 this._lastFrame.paints = this._framePendingActivation.paints; 133 this._framePendingActivation = null; 134 } 135 }, 136 137 handleRequestMainThreadFrame: function() 138 { 139 if (!this._lastFrame) 140 return; 141 this._mainFrameRequested = true; 142 }, 143 144 handleCompositeLayers: function() 145 { 146 if (!this._hasThreadedCompositing || !this._framePendingCommit) 147 return; 148 this._framePendingActivation = this._framePendingCommit; 149 this._framePendingCommit = null; 150 }, 151 152 /** 153 * @param {!WebInspector.DeferredLayerTree} layerTree 154 */ 155 handleLayerTreeSnapshot: function(layerTree) 156 { 157 this._lastLayerTree = layerTree; 158 }, 159 160 /** 161 * @param {number} startTime 162 */ 163 _startBackgroundFrame: function(startTime) 164 { 165 if (!this._hasThreadedCompositing) { 166 this._lastFrame = null; 167 this._hasThreadedCompositing = true; 168 } 169 if (this._lastFrame) 170 this._flushFrame(this._lastFrame, startTime); 171 172 this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._minimumRecordTime); 173 }, 174 175 /** 176 * @param {number} startTime 177 */ 178 _startMainThreadFrame: function(startTime) 179 { 180 if (this._lastFrame) 181 this._flushFrame(this._lastFrame, startTime); 182 this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._minimumRecordTime); 183 }, 184 185 /** 186 * @param {!WebInspector.TimelineFrame} frame 187 * @param {number} endTime 188 */ 189 _flushFrame: function(frame, endTime) 190 { 191 frame._setLayerTree(this._lastLayerTree); 192 frame._setEndTime(endTime); 193 this._frames.push(frame); 194 }, 195 196 /** 197 * @param {!Array.<string>} types 198 * @param {!WebInspector.TimelineModel.Record} record 199 * @return {?WebInspector.TimelineModel.Record} record 200 */ 201 _findRecordRecursively: function(types, record) 202 { 203 if (types.indexOf(record.type()) >= 0) 204 return record; 205 if (!record.children()) 206 return null; 207 for (var i = 0; i < record.children().length; ++i) { 208 var result = this._findRecordRecursively(types, record.children()[i]); 209 if (result) 210 return result; 211 } 212 return null; 213 } 214 } 215 216 /** 217 * @constructor 218 * @extends {WebInspector.TimelineFrameModelBase} 219 */ 220 WebInspector.TimelineFrameModel = function() 221 { 222 WebInspector.TimelineFrameModelBase.call(this); 223 } 224 225 WebInspector.TimelineFrameModel._mainFrameMarkers = [ 226 WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation, 227 WebInspector.TimelineModel.RecordType.InvalidateLayout, 228 WebInspector.TimelineModel.RecordType.BeginFrame, 229 WebInspector.TimelineModel.RecordType.ScrollLayer 230 ]; 231 232 WebInspector.TimelineFrameModel.prototype = { 233 reset: function() 234 { 235 this._mergeRecords = true; 236 this._mergingBuffer = new WebInspector.TimelineMergingRecordBuffer(); 237 WebInspector.TimelineFrameModelBase.prototype.reset.call(this); 238 }, 239 240 /** 241 * @param {boolean} value 242 */ 243 setMergeRecords: function(value) 244 { 245 this._mergeRecords = value; 246 }, 247 248 /** 249 * @param {!Array.<!WebInspector.TimelineModel.Record>} records 250 */ 251 addRecords: function(records) 252 { 253 if (!records.length) 254 return; 255 if (records[0].startTime() < this._minimumRecordTime) 256 this._minimumRecordTime = records[0].startTime(); 257 for (var i = 0; i < records.length; ++i) 258 this.addRecord(records[i]); 259 }, 260 261 /** 262 * @param {!WebInspector.TimelineModel.Record} record 263 */ 264 addRecord: function(record) 265 { 266 var recordTypes = WebInspector.TimelineModel.RecordType; 267 var programRecord = record.type() === recordTypes.Program ? record : null; 268 269 // Start collecting main frame 270 if (programRecord) { 271 if (!this._framePendingCommit && this._findRecordRecursively(WebInspector.TimelineFrameModel._mainFrameMarkers, programRecord)) 272 this._framePendingCommit = new WebInspector.PendingFrame(); 273 } 274 /** type {Array.<!WebInspector.TimelineModel.Record>} */ 275 var records = []; 276 if (!this._mergeRecords) 277 records = [record]; 278 else 279 records = this._mergingBuffer.process(record.thread(), /** type {Array.<!WebInspector.TimelineModel.Record>} */(programRecord ? record.children() || [] : [record])); 280 for (var i = 0; i < records.length; ++i) { 281 if (records[i].thread() === WebInspector.TimelineModel.MainThreadName) 282 this._addMainThreadRecord(programRecord, records[i]); 283 else 284 this._addBackgroundRecord(records[i]); 285 } 286 }, 287 288 /** 289 * @param {!WebInspector.TimelineModel.Record} record 290 */ 291 _addBackgroundRecord: function(record) 292 { 293 var recordTypes = WebInspector.TimelineModel.RecordType; 294 if (record.type() === recordTypes.BeginFrame) 295 this.handleBeginFrame(record.startTime()); 296 else if (record.type() === recordTypes.DrawFrame) 297 this.handleDrawFrame(record.startTime()); 298 else if (record.type() === recordTypes.RequestMainThreadFrame) 299 this.handleRequestMainThreadFrame(); 300 else if (record.type() === recordTypes.ActivateLayerTree) 301 this.handleActivateLayerTree(); 302 303 if (this._lastFrame) 304 this._lastFrame._addTimeFromRecord(record); 305 }, 306 307 /** 308 * @param {?WebInspector.TimelineModel.Record} programRecord 309 * @param {!WebInspector.TimelineModel.Record} record 310 */ 311 _addMainThreadRecord: function(programRecord, record) 312 { 313 var recordTypes = WebInspector.TimelineModel.RecordType; 314 if (record.type() === recordTypes.UpdateLayerTree && record.data()["layerTree"]) 315 this.handleLayerTreeSnapshot(new WebInspector.DeferredAgentLayerTree(record.target(), record.data()["layerTree"])); 316 if (!this._hasThreadedCompositing) { 317 if (record.type() === recordTypes.BeginFrame) 318 this._startMainThreadFrame(record.startTime()); 319 320 if (!this._lastFrame) 321 return; 322 323 this._lastFrame._addTimeFromRecord(record); 324 325 // Account for "other" time at the same time as the first child. 326 if (programRecord.children()[0] === record) 327 this._lastFrame._addTimeForCategory("other", this._deriveOtherTime(programRecord)); 328 return; 329 } 330 331 if (!this._framePendingCommit) 332 return; 333 334 WebInspector.TimelineUIUtilsImpl.aggregateTimeForRecord(this._framePendingCommit.timeByCategory, record); 335 if (programRecord.children()[0] === record) 336 this._framePendingCommit.timeByCategory["other"] = (this._framePendingCommit.timeByCategory["other"] || 0) + this._deriveOtherTime(programRecord); 337 338 if (record.type() === recordTypes.CompositeLayers) 339 this.handleCompositeLayers(); 340 }, 341 342 /** 343 * @param {!WebInspector.TimelineModel.Record} programRecord 344 * @return {number} 345 */ 346 _deriveOtherTime: function(programRecord) 347 { 348 var accounted = 0; 349 for (var i = 0; i < programRecord.children().length; ++i) 350 accounted += programRecord.children()[i].endTime() - programRecord.children()[i].startTime(); 351 return programRecord.endTime() - programRecord.startTime() - accounted; 352 }, 353 354 __proto__: WebInspector.TimelineFrameModelBase.prototype, 355 }; 356 357 /** 358 * @constructor 359 * @extends {WebInspector.TimelineFrameModelBase} 360 */ 361 WebInspector.TracingTimelineFrameModel = function() 362 { 363 WebInspector.TimelineFrameModelBase.call(this); 364 } 365 366 WebInspector.TracingTimelineFrameModel._mainFrameMarkers = [ 367 WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation, 368 WebInspector.TracingTimelineModel.RecordType.InvalidateLayout, 369 WebInspector.TracingTimelineModel.RecordType.BeginMainThreadFrame, 370 WebInspector.TracingTimelineModel.RecordType.ScrollLayer 371 ]; 372 373 WebInspector.TracingTimelineFrameModel.prototype = { 374 /** 375 * @param {!Array.<!WebInspector.TracingModel.Event>} events 376 * @param {string} sessionId 377 */ 378 addTraceEvents: function(events, sessionId) 379 { 380 this._sessionId = sessionId; 381 if (!events.length) 382 return; 383 if (events[0].startTime < this._minimumRecordTime) 384 this._minimumRecordTime = events[0].startTime; 385 for (var i = 0; i < events.length; ++i) 386 this._addTraceEvent(events[i]); 387 }, 388 389 /** 390 * @param {!WebInspector.TracingModel.Event} event 391 */ 392 _addTraceEvent: function(event) 393 { 394 var eventNames = WebInspector.TracingTimelineModel.RecordType; 395 396 if (event.name === eventNames.SetLayerTreeId) { 397 if (this._sessionId === event.args["sessionId"]) 398 this._layerTreeId = event.args["layerTreeId"]; 399 return; 400 } 401 if (event.name === eventNames.TracingStartedInPage) { 402 this._mainThread = event.thread; 403 return; 404 } 405 if (event.thread === this._mainThread) 406 this._addMainThreadTraceEvent(event); 407 else 408 this._addBackgroundTraceEvent(event); 409 }, 410 411 /** 412 * @param {!WebInspector.TracingModel.Event} event 413 */ 414 _addBackgroundTraceEvent: function(event) 415 { 416 var eventNames = WebInspector.TracingTimelineModel.RecordType; 417 if (event.phase === WebInspector.TracingModel.Phase.SnapshotObject && event.name === eventNames.LayerTreeHostImplSnapshot && parseInt(event.id, 0) === this._layerTreeId) { 418 var snapshot = /** @type {!WebInspector.TracingModel.ObjectSnapshot} */ (event); 419 this.handleLayerTreeSnapshot(new WebInspector.DeferredTracingLayerTree(snapshot)); 420 return; 421 } 422 if (this._lastFrame && event.selfTime) 423 this._lastFrame._addTimeForCategory(WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name, event.selfTime); 424 425 if (event.args["layerTreeId"] !== this._layerTreeId) 426 return; 427 428 var timestamp = event.startTime; 429 if (event.name === eventNames.BeginFrame) 430 this.handleBeginFrame(timestamp); 431 else if (event.name === eventNames.DrawFrame) 432 this.handleDrawFrame(timestamp); 433 else if (event.name === eventNames.ActivateLayerTree) 434 this.handleActivateLayerTree(); 435 else if (event.name === eventNames.RequestMainThreadFrame) 436 this.handleRequestMainThreadFrame(); 437 }, 438 439 /** 440 * @param {!WebInspector.TracingModel.Event} event 441 */ 442 _addMainThreadTraceEvent: function(event) 443 { 444 var eventNames = WebInspector.TracingTimelineModel.RecordType; 445 var timestamp = event.startTime; 446 var selfTime = event.selfTime || 0; 447 448 if (!this._hasThreadedCompositing) { 449 if (event.name === eventNames.BeginMainThreadFrame) 450 this._startMainThreadFrame(timestamp); 451 if (!this._lastFrame) 452 return; 453 if (!selfTime) 454 return; 455 456 var categoryName = WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name; 457 this._lastFrame._addTimeForCategory(categoryName, selfTime); 458 return; 459 } 460 461 if (!this._framePendingCommit && WebInspector.TracingTimelineFrameModel._mainFrameMarkers.indexOf(event.name) >= 0) 462 this._framePendingCommit = new WebInspector.PendingFrame(); 463 if (!this._framePendingCommit) 464 return; 465 if (event.name === eventNames.Paint && event.args["data"]["layerId"] && event.picture) 466 this._framePendingCommit.paints.push(new WebInspector.LayerPaintEvent(event)); 467 468 if (selfTime) { 469 var categoryName = WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name; 470 this._framePendingCommit.timeByCategory[categoryName] = (this._framePendingCommit.timeByCategory[categoryName] || 0) + selfTime; 471 } 472 if (event.name === eventNames.CompositeLayers && event.args["layerTreeId"] === this._layerTreeId) 473 this.handleCompositeLayers(); 474 }, 475 476 __proto__: WebInspector.TimelineFrameModelBase.prototype 477 } 478 479 /** 480 * @constructor 481 * @extends {WebInspector.DeferredLayerTree} 482 * @param {!WebInspector.TracingModel.ObjectSnapshot} snapshot 483 */ 484 WebInspector.DeferredTracingLayerTree = function(snapshot) 485 { 486 WebInspector.DeferredLayerTree.call(this, snapshot.thread.target()); 487 this._snapshot = snapshot; 488 } 489 490 WebInspector.DeferredTracingLayerTree.prototype = { 491 /** 492 * @param {function(!WebInspector.LayerTreeBase)} callback 493 */ 494 resolve: function(callback) 495 { 496 this._snapshot.requestObject(onGotObject.bind(this)); 497 /** 498 * @this {WebInspector.DeferredTracingLayerTree} 499 * @param {?Object} result 500 */ 501 function onGotObject(result) 502 { 503 if (!result) 504 return; 505 var viewport = result["device_viewport_size"]; 506 var rootLayer = result["active_tree"]["root_layer"]; 507 var layerTree = new WebInspector.TracingLayerTree(this._target); 508 layerTree.setViewportSize(viewport); 509 layerTree.setLayers(rootLayer, callback.bind(null, layerTree)); 510 } 511 }, 512 513 __proto__: WebInspector.DeferredLayerTree.prototype 514 }; 515 516 517 /** 518 * @constructor 519 * @param {!Array.<!WebInspector.TimelineFrame>} frames 520 */ 521 WebInspector.FrameStatistics = function(frames) 522 { 523 this.frameCount = frames.length; 524 this.minDuration = Infinity; 525 this.maxDuration = 0; 526 this.timeByCategory = {}; 527 this.startOffset = frames[0].startTimeOffset; 528 var lastFrame = frames[this.frameCount - 1]; 529 this.endOffset = lastFrame.startTimeOffset + lastFrame.duration; 530 531 var totalDuration = 0; 532 var sumOfSquares = 0; 533 for (var i = 0; i < this.frameCount; ++i) { 534 var duration = frames[i].duration; 535 totalDuration += duration; 536 sumOfSquares += duration * duration; 537 this.minDuration = Math.min(this.minDuration, duration); 538 this.maxDuration = Math.max(this.maxDuration, duration); 539 WebInspector.FrameStatistics._aggregateTimeByCategory(this.timeByCategory, frames[i].timeByCategory); 540 } 541 this.average = totalDuration / this.frameCount; 542 var variance = sumOfSquares / this.frameCount - this.average * this.average; 543 this.stddev = Math.sqrt(variance); 544 } 545 546 /** 547 * @param {!Object} total 548 * @param {!Object} addend 549 */ 550 WebInspector.FrameStatistics._aggregateTimeByCategory = function(total, addend) 551 { 552 for (var category in addend) 553 total[category] = (total[category] || 0) + addend[category]; 554 } 555 556 /** 557 * @constructor 558 * @param {number} startTime 559 * @param {number} startTimeOffset 560 */ 561 WebInspector.TimelineFrame = function(startTime, startTimeOffset) 562 { 563 this.startTime = startTime; 564 this.startTimeOffset = startTimeOffset; 565 this.endTime = this.startTime; 566 this.duration = 0; 567 this.timeByCategory = {}; 568 this.cpuTime = 0; 569 /** @type {?WebInspector.DeferredLayerTree} */ 570 this.layerTree = null; 571 this.paintTiles = null; 572 } 573 574 WebInspector.TimelineFrame.prototype = { 575 /** 576 * @param {number} endTime 577 */ 578 _setEndTime: function(endTime) 579 { 580 this.endTime = endTime; 581 this.duration = this.endTime - this.startTime; 582 }, 583 584 /** 585 * @param {?WebInspector.DeferredLayerTree} layerTree 586 */ 587 _setLayerTree: function(layerTree) 588 { 589 this.layerTree = layerTree; 590 }, 591 592 /** 593 * @param {!WebInspector.TimelineModel.Record} record 594 */ 595 _addTimeFromRecord: function(record) 596 { 597 if (!record.endTime()) 598 return; 599 var timeByCategory = {}; 600 WebInspector.TimelineUIUtilsImpl.aggregateTimeForRecord(timeByCategory, record); 601 this._addTimeForCategories(timeByCategory); 602 }, 603 604 /** 605 * @param {!Object} timeByCategory 606 */ 607 _addTimeForCategories: function(timeByCategory) 608 { 609 for (var category in timeByCategory) 610 this._addTimeForCategory(category, timeByCategory[category]); 611 }, 612 613 /** 614 * @param {string} category 615 * @param {number} time 616 */ 617 _addTimeForCategory: function(category, time) 618 { 619 this.timeByCategory[category] = (this.timeByCategory[category] || 0) + time; 620 this.cpuTime += time; 621 }, 622 } 623 624 /** 625 * @constructor 626 * @param {!WebInspector.TracingModel.Event} event 627 */ 628 WebInspector.LayerPaintEvent = function(event) 629 { 630 this._event = event; 631 } 632 633 WebInspector.LayerPaintEvent.prototype = { 634 /** 635 * @return {string} 636 */ 637 layerId: function() 638 { 639 return this._event.args["data"]["layerId"]; 640 }, 641 642 /** 643 * @return {!WebInspector.TracingModel.Event} 644 */ 645 event: function() 646 { 647 return this._event; 648 }, 649 650 /** 651 * @param {function(?Array.<number>, ?WebInspector.PaintProfilerSnapshot)} callback 652 */ 653 loadPicture: function(callback) 654 { 655 var target = this._event.thread.target(); 656 this._event.picture.requestObject(onGotObject); 657 /** 658 * @param {?Object} result 659 */ 660 function onGotObject(result) 661 { 662 if (!result || !result["skp64"]) { 663 callback(null, null); 664 return; 665 } 666 var rect = result["params"] && result["params"]["layer_rect"]; 667 WebInspector.PaintProfilerSnapshot.load(target, result["skp64"], callback.bind(null, rect)); 668 } 669 } 670 }; 671 672 /** 673 * @constructor 674 */ 675 WebInspector.PendingFrame = function() 676 { 677 /** @type {!Object.<string, number>} */ 678 this.timeByCategory = {}; 679 /** @type {!Array.<!WebInspector.LayerPaintEvent>} */ 680 this.paints = []; 681 } 682