Home | History | Annotate | Download | only in timeline
      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