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.
33 * @extends {WebInspector.Object}
34 * @param {!WebInspector.TimelineModel} model
36 WebInspector.TimelineFrameModel = function(model)
39 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this);
42 var records = model.records;
43 for (var i = 0; i < records.length; ++i)
44 this._addRecord(records[i]);
47 WebInspector.TimelineFrameModel.Events = {
48 FrameAdded: "FrameAdded"
51 WebInspector.TimelineFrameModel.prototype = {
53 * @return {!Array.<!WebInspector.TimelineFrame>}
63 this._lastFrame = null;
64 this._hasThreadedCompositing = false;
65 this._mainFrameCommitted = false;
66 this._mainFrameRequested = false;
67 this._aggregatedMainThreadWork = null;
68 this._mergingBuffer = new WebInspector.TimelineMergingRecordBuffer();
71 _onRecordAdded: function(event)
73 var record = /** @type {!TimelineAgent.TimelineEvent} */(event.data);
74 this._addRecord(record);
78 * @param {!TimelineAgent.TimelineEvent} record
80 _addRecord: function(record)
82 var recordTypes = WebInspector.TimelineModel.RecordType;
83 var programRecord = record.type === recordTypes.Program ? record : null;
85 // Start collecting main frame
87 if (!this._aggregatedMainThreadWork && this._findRecordRecursively([recordTypes.ScheduleStyleRecalculation, recordTypes.InvalidateLayout, recordTypes.BeginFrame], programRecord))
88 this._aggregatedMainThreadWork = {};
91 var records = this._mergingBuffer.process(record.thread, programRecord ? record.children || [] : [record]);
92 for (var i = 0; i < records.length; ++i) {
93 if (records[i].thread)
94 this._addBackgroundRecord(records[i]);
96 this._addMainThreadRecord(programRecord, records[i]);
101 * @param {!TimelineAgent.TimelineEvent} record
103 _addBackgroundRecord: function(record)
105 var recordTypes = WebInspector.TimelineModel.RecordType;
106 if (!this._lastFrame) {
107 if (record.type === recordTypes.BeginFrame || record.type === recordTypes.DrawFrame)
108 this._startBackgroundFrame(record);
112 if (record.type === recordTypes.DrawFrame) {
113 // - if it wasn't drawn, it didn't happen!
114 // - only show frames that either did not wait for the main thread frame or had one committed.
115 if (this._mainFrameCommitted || !this._mainFrameRequested)
116 this._startBackgroundFrame(record);
117 this._mainFrameCommitted = false;
118 } else if (record.type === recordTypes.RequestMainThreadFrame) {
119 this._mainFrameRequested = true;
120 } else if (record.type === recordTypes.ActivateLayerTree) {
121 this._mainFrameRequested = false;
122 this._mainFrameCommitted = true;
123 this._lastFrame._addTimeForCategories(this._aggregatedMainThreadWorkToAttachToBackgroundFrame);
124 this._aggregatedMainThreadWorkToAttachToBackgroundFrame = {};
126 this._lastFrame._addTimeFromRecord(record);
130 * @param {?TimelineAgent.TimelineEvent} programRecord
131 * @param {!TimelineAgent.TimelineEvent} record
133 _addMainThreadRecord: function(programRecord, record)
135 var recordTypes = WebInspector.TimelineModel.RecordType;
136 if (!this._hasThreadedCompositing) {
137 if (record.type === recordTypes.BeginFrame)
138 this._startMainThreadFrame(record);
140 if (!this._lastFrame)
143 this._lastFrame._addTimeFromRecord(record);
145 // Account for "other" time at the same time as the first child.
146 if (programRecord.children[0] === record) {
147 this._deriveOtherTime(programRecord, this._lastFrame.timeByCategory);
148 this._lastFrame._updateCpuTime();
153 if (!this._aggregatedMainThreadWork)
156 WebInspector.TimelineModel.aggregateTimeForRecord(this._aggregatedMainThreadWork, record);
157 if (programRecord.children[0] === record)
158 this._deriveOtherTime(programRecord, this._aggregatedMainThreadWork);
160 if (record.type === recordTypes.CompositeLayers) {
161 this._aggregatedMainThreadWorkToAttachToBackgroundFrame = this._aggregatedMainThreadWork;
162 this._aggregatedMainThreadWork = null;
167 * @param {!TimelineAgent.TimelineEvent} programRecord
168 * @param {!Object} timeByCategory
170 _deriveOtherTime: function(programRecord, timeByCategory)
173 for (var i = 0; i < programRecord.children.length; ++i)
174 accounted += WebInspector.TimelineModel.durationInSeconds(programRecord.children[i]);
175 var otherTime = WebInspector.TimelineModel.durationInSeconds(programRecord) - accounted;
176 timeByCategory["other"] = (timeByCategory["other"] || 0) + otherTime;
180 * @param {!TimelineAgent.TimelineEvent} record
182 _startBackgroundFrame: function(record)
184 if (!this._hasThreadedCompositing) {
185 this._lastFrame = null;
186 this._hasThreadedCompositing = true;
189 this._flushFrame(this._lastFrame, record);
191 this._lastFrame = new WebInspector.TimelineFrame(this, record);
195 * @param {!TimelineAgent.TimelineEvent} record
197 _startMainThreadFrame: function(record)
200 this._flushFrame(this._lastFrame, record);
201 this._lastFrame = new WebInspector.TimelineFrame(this, record);
205 * @param {!WebInspector.TimelineFrame} frame
206 * @param {!Object} record
208 _flushFrame: function(frame, record)
210 frame._setEndTime(WebInspector.TimelineModel.startTimeInSeconds(record));
211 this._frames.push(frame);
212 this.dispatchEventToListeners(WebInspector.TimelineFrameModel.Events.FrameAdded, frame);
216 * @param {!Array.<string>} types
217 * @param {!TimelineAgent.TimelineEvent} record
218 * @return {?TimelineAgent.TimelineEvent} record
220 _findRecordRecursively: function(types, record)
222 if (types.indexOf(record.type) >= 0)
224 if (!record.children)
226 for (var i = 0; i < record.children.length; ++i) {
227 var result = this._findRecordRecursively(types, record.children[i]);
236 this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this);
239 __proto__: WebInspector.Object.prototype
244 * @param {!Array.<!WebInspector.TimelineFrame>} frames
246 WebInspector.FrameStatistics = function(frames)
248 this.frameCount = frames.length;
249 this.minDuration = Infinity;
250 this.maxDuration = 0;
251 this.timeByCategory = {};
252 this.startOffset = frames[0].startTimeOffset;
253 var lastFrame = frames[this.frameCount - 1];
254 this.endOffset = lastFrame.startTimeOffset + lastFrame.duration;
256 var totalDuration = 0;
257 var sumOfSquares = 0;
258 for (var i = 0; i < this.frameCount; ++i) {
259 var duration = frames[i].duration;
260 totalDuration += duration;
261 sumOfSquares += duration * duration;
262 this.minDuration = Math.min(this.minDuration, duration);
263 this.maxDuration = Math.max(this.maxDuration, duration);
264 WebInspector.TimelineModel.aggregateTimeByCategory(this.timeByCategory, frames[i].timeByCategory);
266 this.average = totalDuration / this.frameCount;
267 var variance = sumOfSquares / this.frameCount - this.average * this.average;
268 this.stddev = Math.sqrt(variance);
273 * @param {!WebInspector.TimelineFrameModel} model
274 * @param {!Object} record
276 WebInspector.TimelineFrame = function(model, record)
278 this.startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
279 this.startTimeOffset = model._model.recordOffsetInSeconds(record);
280 this.endTime = this.startTime;
282 this.timeByCategory = {};
286 WebInspector.TimelineFrame.prototype = {
288 * @param {number} endTime
290 _setEndTime: function(endTime)
292 this.endTime = endTime;
293 this.duration = this.endTime - this.startTime;
297 * @param {!TimelineAgent.TimelineEvent} record
299 _addTimeFromRecord: function(record)
303 WebInspector.TimelineModel.aggregateTimeForRecord(this.timeByCategory, record);
304 this._updateCpuTime();
308 * @param {!Object} timeByCategory
310 _addTimeForCategories: function(timeByCategory)
312 WebInspector.TimelineModel.aggregateTimeByCategory(this.timeByCategory, timeByCategory);
313 this._updateCpuTime();
316 _updateCpuTime: function()
319 for (var key in this.timeByCategory)
320 this.cpuTime += this.timeByCategory[key];