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 * @extends {WebInspector.TargetAwareObject} 34 * @param {!WebInspector.Target} target 35 */ 36 WebInspector.TimelineFrameModelBase = function(target) 37 { 38 WebInspector.TargetAwareObject.call(this, target); 39 40 this.reset(); 41 } 42 43 WebInspector.TimelineFrameModelBase.prototype = { 44 /** 45 * @return {!Array.<!WebInspector.TimelineFrame>} 46 */ 47 frames: function() 48 { 49 return this._frames; 50 }, 51 52 /** 53 * @param {number} startTime 54 * @param {number} endTime 55 * @return {!Array.<!WebInspector.TimelineFrame>} 56 */ 57 filteredFrames: function(startTime, endTime) 58 { 59 /** 60 * @param {number} value 61 * @param {!WebInspector.TimelineFrame} object 62 * @return {number} 63 */ 64 function compareStartTime(value, object) 65 { 66 return value - object.startTime; 67 } 68 /** 69 * @param {number} value 70 * @param {!WebInspector.TimelineFrame} object 71 * @return {number} 72 */ 73 function compareEndTime(value, object) 74 { 75 return value - object.endTime; 76 } 77 var frames = this._frames; 78 var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, frames, compareEndTime); 79 var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, frames, compareStartTime); 80 return frames.slice(firstFrame, lastFrame); 81 }, 82 83 reset: function() 84 { 85 this._minimumRecordTime = Infinity; 86 this._frames = []; 87 this._lastFrame = null; 88 this._lastLayerTree = null; 89 this._hasThreadedCompositing = false; 90 this._mainFrameCommitted = false; 91 this._mainFrameRequested = false; 92 this._aggregatedMainThreadWork = null; 93 }, 94 95 /** 96 * @param {number} startTime 97 */ 98 handleBeginFrame: function(startTime) 99 { 100 if (!this._lastFrame) 101 this._startBackgroundFrame(startTime); 102 }, 103 104 /** 105 * @param {number} startTime 106 */ 107 handleDrawFrame: function(startTime) 108 { 109 if (!this._lastFrame) { 110 this._startBackgroundFrame(startTime); 111 return; 112 } 113 114 // - if it wasn't drawn, it didn't happen! 115 // - only show frames that either did not wait for the main thread frame or had one committed. 116 if (this._mainFrameCommitted || !this._mainFrameRequested) 117 this._startBackgroundFrame(startTime); 118 this._mainFrameCommitted = false; 119 }, 120 121 handleActivateLayerTree: function() 122 { 123 if (!this._lastFrame) 124 return; 125 this._mainFrameRequested = false; 126 this._mainFrameCommitted = true; 127 this._lastFrame._addTimeForCategories(this._aggregatedMainThreadWorkToAttachToBackgroundFrame); 128 this._aggregatedMainThreadWorkToAttachToBackgroundFrame = {}; 129 }, 130 131 handleRequestMainThreadFrame: function() 132 { 133 if (!this._lastFrame) 134 return; 135 this._mainFrameRequested = true; 136 }, 137 138 handleCompositeLayers: function() 139 { 140 if (!this._hasThreadedCompositing || !this._aggregatedMainThreadWork) 141 return; 142 this._aggregatedMainThreadWorkToAttachToBackgroundFrame = this._aggregatedMainThreadWork; 143 this._aggregatedMainThreadWork = null; 144 }, 145 146 /** 147 * @param {!WebInspector.DeferredLayerTree} layerTree 148 */ 149 handleLayerTreeSnapshot: function(layerTree) 150 { 151 this._lastLayerTree = layerTree; 152 }, 153 154 /** 155 * @param {number} startTime 156 */ 157 _startBackgroundFrame: function(startTime) 158 { 159 if (!this._hasThreadedCompositing) { 160 this._lastFrame = null; 161 this._hasThreadedCompositing = true; 162 } 163 if (this._lastFrame) 164 this._flushFrame(this._lastFrame, startTime); 165 166 this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._minimumRecordTime); 167 }, 168 169 /** 170 * @param {number} startTime 171 */ 172 _startMainThreadFrame: function(startTime) 173 { 174 if (this._lastFrame) 175 this._flushFrame(this._lastFrame, startTime); 176 this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._minimumRecordTime); 177 }, 178 179 /** 180 * @param {!WebInspector.TimelineFrame} frame 181 * @param {number} endTime 182 */ 183 _flushFrame: function(frame, endTime) 184 { 185 frame._setLayerTree(this._lastLayerTree); 186 frame._setEndTime(endTime); 187 this._frames.push(frame); 188 }, 189 190 /** 191 * @param {!Array.<string>} types 192 * @param {!WebInspector.TimelineModel.Record} record 193 * @return {?WebInspector.TimelineModel.Record} record 194 */ 195 _findRecordRecursively: function(types, record) 196 { 197 if (types.indexOf(record.type()) >= 0) 198 return record; 199 if (!record.children()) 200 return null; 201 for (var i = 0; i < record.children().length; ++i) { 202 var result = this._findRecordRecursively(types, record.children()[i]); 203 if (result) 204 return result; 205 } 206 return null; 207 }, 208 209 __proto__: WebInspector.TargetAwareObject.prototype 210 } 211 212 /** 213 * @constructor 214 * @param {!WebInspector.Target} target 215 * @extends {WebInspector.TimelineFrameModelBase} 216 */ 217 WebInspector.TimelineFrameModel = function(target) 218 { 219 WebInspector.TimelineFrameModelBase.call(this, target); 220 } 221 222 WebInspector.TimelineFrameModel._mainFrameMarkers = [ 223 WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation, 224 WebInspector.TimelineModel.RecordType.InvalidateLayout, 225 WebInspector.TimelineModel.RecordType.BeginFrame, 226 WebInspector.TimelineModel.RecordType.ScrollLayer 227 ]; 228 229 WebInspector.TimelineFrameModel.prototype = { 230 reset: function() 231 { 232 this._mergeRecords = true; 233 this._mergingBuffer = new WebInspector.TimelineMergingRecordBuffer(); 234 WebInspector.TimelineFrameModelBase.prototype.reset.call(this); 235 }, 236 237 /** 238 * @param {boolean} value 239 */ 240 setMergeRecords: function(value) 241 { 242 this._mergeRecords = value; 243 }, 244 245 /** 246 * @param {!Array.<!WebInspector.TimelineModel.Record>} records 247 */ 248 addRecords: function(records) 249 { 250 if (!records.length) 251 return; 252 if (records[0].startTime() < this._minimumRecordTime) 253 this._minimumRecordTime = records[0].startTime(); 254 for (var i = 0; i < records.length; ++i) 255 this.addRecord(records[i]); 256 }, 257 258 /** 259 * @param {!WebInspector.TimelineModel.Record} record 260 */ 261 addRecord: function(record) 262 { 263 var recordTypes = WebInspector.TimelineModel.RecordType; 264 var programRecord = record.type() === recordTypes.Program ? record : null; 265 266 // Start collecting main frame 267 if (programRecord) { 268 if (!this._aggregatedMainThreadWork && this._findRecordRecursively(WebInspector.TimelineFrameModel._mainFrameMarkers, programRecord)) 269 this._aggregatedMainThreadWork = {}; 270 } 271 /** type {Array.<!WebInspector.TimelineModel.Record>} */ 272 var records = []; 273 if (!this._mergeRecords) 274 records = [record]; 275 else 276 records = this._mergingBuffer.process(record.thread(), /** type {Array.<!WebInspector.TimelineModel.Record>} */(programRecord ? record.children() || [] : [record])); 277 for (var i = 0; i < records.length; ++i) { 278 if (records[i].thread()) 279 this._addBackgroundRecord(records[i]); 280 else 281 this._addMainThreadRecord(programRecord, records[i]); 282 } 283 }, 284 285 /** 286 * @param {!WebInspector.TimelineModel.Record} record 287 */ 288 _addBackgroundRecord: function(record) 289 { 290 var recordTypes = WebInspector.TimelineModel.RecordType; 291 if (record.type() === recordTypes.BeginFrame) 292 this.handleBeginFrame(record.startTime()); 293 else if (record.type() === recordTypes.DrawFrame) 294 this.handleDrawFrame(record.startTime()); 295 else if (record.type() === recordTypes.RequestMainThreadFrame) 296 this.handleRequestMainThreadFrame(); 297 else if (record.type() === recordTypes.ActivateLayerTree) 298 this.handleActivateLayerTree(); 299 300 if (this._lastFrame) 301 this._lastFrame._addTimeFromRecord(record); 302 }, 303 304 /** 305 * @param {?WebInspector.TimelineModel.Record} programRecord 306 * @param {!WebInspector.TimelineModel.Record} record 307 */ 308 _addMainThreadRecord: function(programRecord, record) 309 { 310 var recordTypes = WebInspector.TimelineModel.RecordType; 311 if (record.type() === recordTypes.UpdateLayerTree && record.data()["layerTree"]) 312 this.handleLayerTreeSnapshot(new WebInspector.DeferredAgentLayerTree(this.target(), record.data()["layerTree"])); 313 if (!this._hasThreadedCompositing) { 314 if (record.type() === recordTypes.BeginFrame) 315 this._startMainThreadFrame(record.startTime()); 316 317 if (!this._lastFrame) 318 return; 319 320 this._lastFrame._addTimeFromRecord(record); 321 322 // Account for "other" time at the same time as the first child. 323 if (programRecord.children()[0] === record) 324 this._lastFrame._addTimeForCategory("other", this._deriveOtherTime(programRecord)); 325 return; 326 } 327 328 if (!this._aggregatedMainThreadWork) 329 return; 330 331 WebInspector.TimelineUIUtils.aggregateTimeForRecord(this._aggregatedMainThreadWork, record); 332 if (programRecord.children()[0] === record) 333 this._aggregatedMainThreadWork["other"] = (this._aggregatedMainThreadWork["other"] || 0) + this._deriveOtherTime(programRecord); 334 335 if (record.type() === recordTypes.CompositeLayers) 336 this.handleCompositeLayers(); 337 }, 338 339 /** 340 * @param {!WebInspector.TimelineModel.Record} programRecord 341 * @return {number} 342 */ 343 _deriveOtherTime: function(programRecord) 344 { 345 var accounted = 0; 346 for (var i = 0; i < programRecord.children().length; ++i) 347 accounted += programRecord.children()[i].endTime() - programRecord.children()[i].startTime(); 348 return programRecord.endTime() - programRecord.startTime() - accounted; 349 }, 350 351 __proto__: WebInspector.TimelineFrameModelBase.prototype, 352 }; 353 354 /** 355 * @constructor 356 * @param {!WebInspector.Target} target 357 * @extends {WebInspector.TimelineFrameModelBase} 358 */ 359 WebInspector.TracingTimelineFrameModel = function(target) 360 { 361 WebInspector.TimelineFrameModelBase.call(this, target); 362 } 363 364 WebInspector.TracingTimelineFrameModel._mainFrameMarkers = [ 365 WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation, 366 WebInspector.TracingTimelineModel.RecordType.InvalidateLayout, 367 WebInspector.TracingTimelineModel.RecordType.BeginMainThreadFrame, 368 WebInspector.TracingTimelineModel.RecordType.ScrollLayer 369 ]; 370 371 WebInspector.TracingTimelineFrameModel.prototype = { 372 /** 373 * @param {!Array.<!WebInspector.TracingModel.Event>} events 374 * @param {string} sessionId 375 */ 376 addTraceEvents: function(events, sessionId) 377 { 378 this._sessionId = sessionId; 379 if (!events.length) 380 return; 381 if (events[0].startTime < this._minimumRecordTime) 382 this._minimumRecordTime = events[0].startTime; 383 for (var i = 0; i < events.length; ++i) 384 this._addTraceEvent(events[i]); 385 }, 386 387 /** 388 * @param {!WebInspector.TracingModel.Event} event 389 */ 390 _addTraceEvent: function(event) 391 { 392 var eventNames = WebInspector.TracingTimelineModel.RecordType; 393 394 if (event.name === eventNames.SetLayerTreeId) { 395 if (this._sessionId === event.args["sessionId"]) 396 this._layerTreeId = event.args["layerTreeId"]; 397 return; 398 } 399 if (event.name === eventNames.TracingStartedInPage) { 400 this._mainThread = event.thread; 401 return; 402 } 403 if (event.thread === this._mainThread) 404 this._addMainThreadTraceEvent(event); 405 else 406 this._addBackgroundTraceEvent(event); 407 }, 408 409 /** 410 * @param {!WebInspector.TracingModel.Event} event 411 */ 412 _addBackgroundTraceEvent: function(event) 413 { 414 var eventNames = WebInspector.TracingTimelineModel.RecordType; 415 416 if (event.phase === WebInspector.TracingModel.Phase.SnapshotObject && event.name === eventNames.LayerTreeHostImplSnapshot && parseInt(event.id, 0) === this._layerTreeId) { 417 this.handleLayerTreeSnapshot(new WebInspector.DeferredTracingLayerTree(this.target(), event.args["snapshot"]["active_tree"]["root_layer"], event.args["snapshot"]["device_viewport_size"])); 418 return; 419 } 420 if (this._lastFrame && event.selfTime) 421 this._lastFrame._addTimeForCategory(WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name, event.selfTime); 422 423 if (event.args["layerTreeId"] !== this._layerTreeId) 424 return; 425 426 var timestamp = event.startTime; 427 if (event.name === eventNames.BeginFrame) 428 this.handleBeginFrame(timestamp); 429 else if (event.name === eventNames.DrawFrame) 430 this.handleDrawFrame(timestamp); 431 else if (event.name === eventNames.ActivateLayerTree) 432 this.handleActivateLayerTree(); 433 else if (event.name === eventNames.RequestMainThreadFrame) 434 this.handleRequestMainThreadFrame(); 435 }, 436 437 /** 438 * @param {!WebInspector.TracingModel.Event} event 439 */ 440 _addMainThreadTraceEvent: function(event) 441 { 442 var eventNames = WebInspector.TracingTimelineModel.RecordType; 443 var timestamp = event.startTime; 444 var selfTime = event.selfTime || 0; 445 446 if (!this._hasThreadedCompositing) { 447 if (event.name === eventNames.BeginMainThreadFrame) 448 this._startMainThreadFrame(timestamp); 449 if (!this._lastFrame) 450 return; 451 if (!selfTime) 452 return; 453 454 var categoryName = WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name; 455 this._lastFrame._addTimeForCategory(categoryName, selfTime); 456 return; 457 } 458 459 if (!this._aggregatedMainThreadWork && WebInspector.TracingTimelineFrameModel._mainFrameMarkers.indexOf(event.name) >= 0) 460 this._aggregatedMainThreadWork = {}; 461 if (!this._aggregatedMainThreadWork) 462 return; 463 464 if (selfTime) { 465 var categoryName = WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name; 466 this._aggregatedMainThreadWork[categoryName] = (this._aggregatedMainThreadWork[categoryName] || 0) + selfTime; 467 } 468 if (event.name === eventNames.CompositeLayers && event.args["layerTreeId"] === this._layerTreeId) 469 this.handleCompositeLayers(); 470 }, 471 472 __proto__: WebInspector.TimelineFrameModelBase.prototype 473 } 474 475 /** 476 * @constructor 477 * @param {!Array.<!WebInspector.TimelineFrame>} frames 478 */ 479 WebInspector.FrameStatistics = function(frames) 480 { 481 this.frameCount = frames.length; 482 this.minDuration = Infinity; 483 this.maxDuration = 0; 484 this.timeByCategory = {}; 485 this.startOffset = frames[0].startTimeOffset; 486 var lastFrame = frames[this.frameCount - 1]; 487 this.endOffset = lastFrame.startTimeOffset + lastFrame.duration; 488 489 var totalDuration = 0; 490 var sumOfSquares = 0; 491 for (var i = 0; i < this.frameCount; ++i) { 492 var duration = frames[i].duration; 493 totalDuration += duration; 494 sumOfSquares += duration * duration; 495 this.minDuration = Math.min(this.minDuration, duration); 496 this.maxDuration = Math.max(this.maxDuration, duration); 497 WebInspector.TimelineUIUtils.aggregateTimeByCategory(this.timeByCategory, frames[i].timeByCategory); 498 } 499 this.average = totalDuration / this.frameCount; 500 var variance = sumOfSquares / this.frameCount - this.average * this.average; 501 this.stddev = Math.sqrt(variance); 502 } 503 504 /** 505 * @constructor 506 * @param {number} startTime 507 * @param {number} startTimeOffset 508 */ 509 WebInspector.TimelineFrame = function(startTime, startTimeOffset) 510 { 511 this.startTime = startTime; 512 this.startTimeOffset = startTimeOffset; 513 this.endTime = this.startTime; 514 this.duration = 0; 515 this.timeByCategory = {}; 516 this.cpuTime = 0; 517 /** @type {?WebInspector.DeferredLayerTree} */ 518 this.layerTree = null; 519 } 520 521 WebInspector.TimelineFrame.prototype = { 522 /** 523 * @param {number} endTime 524 */ 525 _setEndTime: function(endTime) 526 { 527 this.endTime = endTime; 528 this.duration = this.endTime - this.startTime; 529 }, 530 531 /** 532 * @param {?WebInspector.DeferredLayerTree} layerTree 533 */ 534 _setLayerTree: function(layerTree) 535 { 536 this.layerTree = layerTree; 537 }, 538 539 /** 540 * @param {!WebInspector.TimelineModel.Record} record 541 */ 542 _addTimeFromRecord: function(record) 543 { 544 if (!record.endTime()) 545 return; 546 var timeByCategory = {}; 547 WebInspector.TimelineUIUtils.aggregateTimeForRecord(timeByCategory, record); 548 this._addTimeForCategories(timeByCategory); 549 }, 550 551 /** 552 * @param {!Object} timeByCategory 553 */ 554 _addTimeForCategories: function(timeByCategory) 555 { 556 for (var category in timeByCategory) 557 this._addTimeForCategory(category, timeByCategory[category]); 558 }, 559 560 /** 561 * @param {string} category 562 * @param {number} time 563 */ 564 _addTimeForCategory: function(category, time) 565 { 566 this.timeByCategory[category] = (this.timeByCategory[category] || 0) + time; 567 this.cpuTime += time; 568 }, 569 } 570