2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
34 WebInspector.TimelineFrameModelBase = function()
39 WebInspector.TimelineFrameModelBase.prototype = {
41 * @param {boolean} value
43 setMergeRecords: function(value)
48 * @return {!Array.<!WebInspector.TimelineFrame>}
56 * @param {number} startTime
57 * @param {number} endTime
58 * @return {!Array.<!WebInspector.TimelineFrame>}
60 filteredFrames: function(startTime, endTime)
63 * @param {number} value
64 * @param {!WebInspector.TimelineFrame} object
67 function compareStartTime(value, object)
69 return value - object.startTime;
72 * @param {number} value
73 * @param {!WebInspector.TimelineFrame} object
76 function compareEndTime(value, object)
78 return value - object.endTime;
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);
88 this._minimumRecordTime = Infinity;
90 this._lastFrame = null;
91 this._lastLayerTree = null;
92 this._hasThreadedCompositing = false;
93 this._mainFrameCommitted = false;
94 this._mainFrameRequested = false;
95 this._framePendingCommit = null;
99 * @param {number} startTime
101 handleBeginFrame: function(startTime)
103 if (!this._lastFrame)
104 this._startBackgroundFrame(startTime);
108 * @param {number} startTime
110 handleDrawFrame: function(startTime)
112 if (!this._lastFrame) {
113 this._startBackgroundFrame(startTime);
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;
124 handleActivateLayerTree: function()
126 if (!this._lastFrame)
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;
137 handleRequestMainThreadFrame: function()
139 if (!this._lastFrame)
141 this._mainFrameRequested = true;
144 handleCompositeLayers: function()
146 if (!this._hasThreadedCompositing || !this._framePendingCommit)
148 this._framePendingActivation = this._framePendingCommit;
149 this._framePendingCommit = null;
153 * @param {!WebInspector.DeferredLayerTree} layerTree
155 handleLayerTreeSnapshot: function(layerTree)
157 this._lastLayerTree = layerTree;
161 * @param {number} startTime
163 _startBackgroundFrame: function(startTime)
165 if (!this._hasThreadedCompositing) {
166 this._lastFrame = null;
167 this._hasThreadedCompositing = true;
170 this._flushFrame(this._lastFrame, startTime);
172 this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._minimumRecordTime);
176 * @param {number} startTime
178 _startMainThreadFrame: function(startTime)
181 this._flushFrame(this._lastFrame, startTime);
182 this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._minimumRecordTime);
186 * @param {!WebInspector.TimelineFrame} frame
187 * @param {number} endTime
189 _flushFrame: function(frame, endTime)
191 frame._setLayerTree(this._lastLayerTree);
192 frame._setEndTime(endTime);
193 this._frames.push(frame);
197 * @param {!Array.<string>} types
198 * @param {!WebInspector.TimelineModel.Record} record
199 * @return {?WebInspector.TimelineModel.Record} record
201 _findRecordRecursively: function(types, record)
203 if (types.indexOf(record.type()) >= 0)
205 if (!record.children())
207 for (var i = 0; i < record.children().length; ++i) {
208 var result = this._findRecordRecursively(types, record.children()[i]);
218 * @extends {WebInspector.TimelineFrameModelBase}
220 WebInspector.TimelineFrameModel = function()
222 WebInspector.TimelineFrameModelBase.call(this);
225 WebInspector.TimelineFrameModel._mainFrameMarkers = [
226 WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation,
227 WebInspector.TimelineModel.RecordType.InvalidateLayout,
228 WebInspector.TimelineModel.RecordType.BeginFrame,
229 WebInspector.TimelineModel.RecordType.ScrollLayer
232 WebInspector.TimelineFrameModel.prototype = {
235 this._mergeRecords = true;
236 this._mergingBuffer = new WebInspector.TimelineMergingRecordBuffer();
237 WebInspector.TimelineFrameModelBase.prototype.reset.call(this);
241 * @param {boolean} value
243 setMergeRecords: function(value)
245 this._mergeRecords = value;
249 * @param {!Array.<!WebInspector.TimelineModel.Record>} records
251 addRecords: function(records)
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]);
262 * @param {!WebInspector.TimelineModel.Record} record
264 addRecord: function(record)
266 var recordTypes = WebInspector.TimelineModel.RecordType;
267 var programRecord = record.type() === recordTypes.Program ? record : null;
269 // Start collecting main frame
271 if (!this._framePendingCommit && this._findRecordRecursively(WebInspector.TimelineFrameModel._mainFrameMarkers, programRecord))
272 this._framePendingCommit = new WebInspector.PendingFrame();
274 /** type {Array.<!WebInspector.TimelineModel.Record>} */
276 if (!this._mergeRecords)
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]);
284 this._addBackgroundRecord(records[i]);
289 * @param {!WebInspector.TimelineModel.Record} record
291 _addBackgroundRecord: function(record)
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();
304 this._lastFrame._addTimeFromRecord(record);
308 * @param {?WebInspector.TimelineModel.Record} programRecord
309 * @param {!WebInspector.TimelineModel.Record} record
311 _addMainThreadRecord: function(programRecord, record)
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());
320 if (!this._lastFrame)
323 this._lastFrame._addTimeFromRecord(record);
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));
331 if (!this._framePendingCommit)
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);
338 if (record.type() === recordTypes.CompositeLayers)
339 this.handleCompositeLayers();
343 * @param {!WebInspector.TimelineModel.Record} programRecord
346 _deriveOtherTime: function(programRecord)
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;
354 __proto__: WebInspector.TimelineFrameModelBase.prototype,
359 * @extends {WebInspector.TimelineFrameModelBase}
361 WebInspector.TracingTimelineFrameModel = function()
363 WebInspector.TimelineFrameModelBase.call(this);
366 WebInspector.TracingTimelineFrameModel._mainFrameMarkers = [
367 WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation,
368 WebInspector.TracingTimelineModel.RecordType.InvalidateLayout,
369 WebInspector.TracingTimelineModel.RecordType.BeginMainThreadFrame,
370 WebInspector.TracingTimelineModel.RecordType.ScrollLayer
373 WebInspector.TracingTimelineFrameModel.prototype = {
375 * @param {!Array.<!WebInspector.TracingModel.Event>} events
376 * @param {string} sessionId
378 addTraceEvents: function(events, sessionId)
380 this._sessionId = sessionId;
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]);
390 * @param {!WebInspector.TracingModel.Event} event
392 _addTraceEvent: function(event)
394 var eventNames = WebInspector.TracingTimelineModel.RecordType;
396 if (event.name === eventNames.SetLayerTreeId) {
397 if (this._sessionId === event.args["sessionId"])
398 this._layerTreeId = event.args["layerTreeId"];
401 if (event.name === eventNames.TracingStartedInPage) {
402 this._mainThread = event.thread;
405 if (event.thread === this._mainThread)
406 this._addMainThreadTraceEvent(event);
408 this._addBackgroundTraceEvent(event);
412 * @param {!WebInspector.TracingModel.Event} event
414 _addBackgroundTraceEvent: function(event)
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));
422 if (this._lastFrame && event.selfTime)
423 this._lastFrame._addTimeForCategory(WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name, event.selfTime);
425 if (event.args["layerTreeId"] !== this._layerTreeId)
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();
440 * @param {!WebInspector.TracingModel.Event} event
442 _addMainThreadTraceEvent: function(event)
444 var eventNames = WebInspector.TracingTimelineModel.RecordType;
445 var timestamp = event.startTime;
446 var selfTime = event.selfTime || 0;
448 if (!this._hasThreadedCompositing) {
449 if (event.name === eventNames.BeginMainThreadFrame)
450 this._startMainThreadFrame(timestamp);
451 if (!this._lastFrame)
456 var categoryName = WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name;
457 this._lastFrame._addTimeForCategory(categoryName, selfTime);
461 if (!this._framePendingCommit && WebInspector.TracingTimelineFrameModel._mainFrameMarkers.indexOf(event.name) >= 0)
462 this._framePendingCommit = new WebInspector.PendingFrame();
463 if (!this._framePendingCommit)
465 if (event.name === eventNames.Paint && event.args["data"]["layerId"] && event.picture)
466 this._framePendingCommit.paints.push(new WebInspector.LayerPaintEvent(event));
469 var categoryName = WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name;
470 this._framePendingCommit.timeByCategory[categoryName] = (this._framePendingCommit.timeByCategory[categoryName] || 0) + selfTime;
472 if (event.name === eventNames.CompositeLayers && event.args["layerTreeId"] === this._layerTreeId)
473 this.handleCompositeLayers();
476 __proto__: WebInspector.TimelineFrameModelBase.prototype
481 * @extends {WebInspector.DeferredLayerTree}
482 * @param {!WebInspector.TracingModel.ObjectSnapshot} snapshot
484 WebInspector.DeferredTracingLayerTree = function(snapshot)
486 WebInspector.DeferredLayerTree.call(this, snapshot.thread.target());
487 this._snapshot = snapshot;
490 WebInspector.DeferredTracingLayerTree.prototype = {
492 * @param {function(!WebInspector.LayerTreeBase)} callback
494 resolve: function(callback)
496 this._snapshot.requestObject(onGotObject.bind(this));
498 * @this {WebInspector.DeferredTracingLayerTree}
499 * @param {?Object} result
501 function onGotObject(result)
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));
513 __proto__: WebInspector.DeferredLayerTree.prototype
519 * @param {!Array.<!WebInspector.TimelineFrame>} frames
521 WebInspector.FrameStatistics = function(frames)
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;
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);
541 this.average = totalDuration / this.frameCount;
542 var variance = sumOfSquares / this.frameCount - this.average * this.average;
543 this.stddev = Math.sqrt(variance);
547 * @param {!Object} total
548 * @param {!Object} addend
550 WebInspector.FrameStatistics._aggregateTimeByCategory = function(total, addend)
552 for (var category in addend)
553 total[category] = (total[category] || 0) + addend[category];
558 * @param {number} startTime
559 * @param {number} startTimeOffset
561 WebInspector.TimelineFrame = function(startTime, startTimeOffset)
563 this.startTime = startTime;
564 this.startTimeOffset = startTimeOffset;
565 this.endTime = this.startTime;
567 this.timeByCategory = {};
569 /** @type {?WebInspector.DeferredLayerTree} */
570 this.layerTree = null;
571 this.paintTiles = null;
574 WebInspector.TimelineFrame.prototype = {
576 * @param {number} endTime
578 _setEndTime: function(endTime)
580 this.endTime = endTime;
581 this.duration = this.endTime - this.startTime;
585 * @param {?WebInspector.DeferredLayerTree} layerTree
587 _setLayerTree: function(layerTree)
589 this.layerTree = layerTree;
593 * @param {!WebInspector.TimelineModel.Record} record
595 _addTimeFromRecord: function(record)
597 if (!record.endTime())
599 var timeByCategory = {};
600 WebInspector.TimelineUIUtilsImpl.aggregateTimeForRecord(timeByCategory, record);
601 this._addTimeForCategories(timeByCategory);
605 * @param {!Object} timeByCategory
607 _addTimeForCategories: function(timeByCategory)
609 for (var category in timeByCategory)
610 this._addTimeForCategory(category, timeByCategory[category]);
614 * @param {string} category
615 * @param {number} time
617 _addTimeForCategory: function(category, time)
619 this.timeByCategory[category] = (this.timeByCategory[category] || 0) + time;
620 this.cpuTime += time;
626 * @param {!WebInspector.TracingModel.Event} event
628 WebInspector.LayerPaintEvent = function(event)
633 WebInspector.LayerPaintEvent.prototype = {
639 return this._event.args["data"]["layerId"];
643 * @return {!WebInspector.TracingModel.Event}
651 * @param {function(?Array.<number>, ?WebInspector.PaintProfilerSnapshot)} callback
653 loadPicture: function(callback)
655 var target = this._event.thread.target();
656 this._event.picture.requestObject(onGotObject);
658 * @param {?Object} result
660 function onGotObject(result)
662 if (!result || !result["skp64"] || !target) {
663 callback(null, null);
666 var rect = result["params"] && result["params"]["layer_rect"];
667 WebInspector.PaintProfilerSnapshot.load(target, result["skp64"], callback.bind(null, rect));
675 WebInspector.PendingFrame = function()
677 /** @type {!Object.<string, number>} */
678 this.timeByCategory = {};
679 /** @type {!Array.<!WebInspector.LayerPaintEvent>} */