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 */
34WebInspector.TimelineFrameModelBase = function()
35{
36    this.reset();
37}
38
39WebInspector.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 */
220WebInspector.TimelineFrameModel = function()
221{
222    WebInspector.TimelineFrameModelBase.call(this);
223}
224
225WebInspector.TimelineFrameModel._mainFrameMarkers = [
226    WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation,
227    WebInspector.TimelineModel.RecordType.InvalidateLayout,
228    WebInspector.TimelineModel.RecordType.BeginFrame,
229    WebInspector.TimelineModel.RecordType.ScrollLayer
230];
231
232WebInspector.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 */
361WebInspector.TracingTimelineFrameModel = function()
362{
363    WebInspector.TimelineFrameModelBase.call(this);
364}
365
366WebInspector.TracingTimelineFrameModel._mainFrameMarkers = [
367    WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation,
368    WebInspector.TracingTimelineModel.RecordType.InvalidateLayout,
369    WebInspector.TracingTimelineModel.RecordType.BeginMainThreadFrame,
370    WebInspector.TracingTimelineModel.RecordType.ScrollLayer
371];
372
373WebInspector.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 */
484WebInspector.DeferredTracingLayerTree = function(snapshot)
485{
486    WebInspector.DeferredLayerTree.call(this, snapshot.thread.target());
487    this._snapshot = snapshot;
488}
489
490WebInspector.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 */
521WebInspector.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 */
550WebInspector.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 */
561WebInspector.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
574WebInspector.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 */
628WebInspector.LayerPaintEvent = function(event)
629{
630    this._event = event;
631}
632
633WebInspector.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 */
675WebInspector.PendingFrame = function()
676{
677    /** @type {!Object.<string, number>} */
678    this.timeByCategory = {};
679    /** @type {!Array.<!WebInspector.LayerPaintEvent>} */
680    this.paints = [];
681}
682