• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
36WebInspector.TimelineFrameModelBase = function(target)
37{
38    WebInspector.TargetAwareObject.call(this, target);
39
40    this.reset();
41}
42
43WebInspector.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 */
217WebInspector.TimelineFrameModel = function(target)
218{
219    WebInspector.TimelineFrameModelBase.call(this, target);
220}
221
222WebInspector.TimelineFrameModel._mainFrameMarkers = [
223    WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation,
224    WebInspector.TimelineModel.RecordType.InvalidateLayout,
225    WebInspector.TimelineModel.RecordType.BeginFrame,
226    WebInspector.TimelineModel.RecordType.ScrollLayer
227];
228
229WebInspector.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 */
359WebInspector.TracingTimelineFrameModel = function(target)
360{
361    WebInspector.TimelineFrameModelBase.call(this, target);
362}
363
364WebInspector.TracingTimelineFrameModel._mainFrameMarkers = [
365    WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation,
366    WebInspector.TracingTimelineModel.RecordType.InvalidateLayout,
367    WebInspector.TracingTimelineModel.RecordType.BeginMainThreadFrame,
368    WebInspector.TracingTimelineModel.RecordType.ScrollLayer
369];
370
371WebInspector.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 */
479WebInspector.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 */
509WebInspector.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
521WebInspector.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