Home | History | Annotate | Download | only in timeline
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  * Copyright (C) 2012 Intel Inc. All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /**
     33  * @constructor
     34  * @extends {WebInspector.Object}
     35  * @param {!WebInspector.TimelineModel} model
     36  * @param {!WebInspector.TimelineUIUtils} uiUtils
     37  */
     38 WebInspector.TimelinePresentationModel = function(model, uiUtils)
     39 {
     40     this._model = model;
     41     this._uiUtils = uiUtils;
     42     this._filters = [];
     43     /**
     44      * @type {!Map.<!WebInspector.TimelineModel.Record, !WebInspector.TimelinePresentationModel.Record>}
     45      */
     46     this._recordToPresentationRecord = new Map();
     47     this.reset();
     48 }
     49 
     50 WebInspector.TimelinePresentationModel.prototype = {
     51     /**
     52      * @param {number} startTime
     53      * @param {number} endTime
     54      */
     55     setWindowTimes: function(startTime, endTime)
     56     {
     57         this._windowStartTime = startTime;
     58         this._windowEndTime = endTime;
     59     },
     60 
     61     /**
     62      * @param {?WebInspector.TimelineModel.Record} record
     63      * @return {?WebInspector.TimelinePresentationModel.Record}
     64      */
     65     toPresentationRecord: function(record)
     66     {
     67         return record ? this._recordToPresentationRecord.get(record) || null : null;
     68     },
     69 
     70     /**
     71      * @return {!WebInspector.TimelinePresentationModel.Record}
     72      */
     73     rootRecord: function()
     74     {
     75         return this._rootRecord;
     76     },
     77 
     78     reset: function()
     79     {
     80         this._recordToPresentationRecord.clear();
     81         this._rootRecord = new WebInspector.TimelinePresentationModel.RootRecord();
     82         /** @type {!Object.<string, !WebInspector.TimelinePresentationModel.Record>} */
     83         this._coalescingBuckets = {};
     84     },
     85 
     86     /**
     87      * @param {!WebInspector.TimelineModel.Record} record
     88      */
     89     addRecord: function(record)
     90     {
     91         if (this._uiUtils.isProgram(record)) {
     92             var records = record.children();
     93             for (var i = 0; i < records.length; ++i)
     94                 this._innerAddRecord(this._rootRecord, records[i]);
     95         } else {
     96             this._innerAddRecord(this._rootRecord, record);
     97         }
     98     },
     99 
    100     /**
    101      * @param {!WebInspector.TimelinePresentationModel.Record} parentRecord
    102      * @param {!WebInspector.TimelineModel.Record} record
    103      */
    104     _innerAddRecord: function(parentRecord, record)
    105     {
    106         var coalescingBucket;
    107 
    108         // On main thread, only coalesce if the last event is of same type.
    109         if (parentRecord === this._rootRecord)
    110             coalescingBucket = record.thread() ? record.type() : "mainThread";
    111         var coalescedRecord = this._findCoalescedParent(record, parentRecord, coalescingBucket);
    112         if (coalescedRecord)
    113             parentRecord = coalescedRecord;
    114 
    115         var formattedRecord = new WebInspector.TimelinePresentationModel.ActualRecord(record, parentRecord);
    116         this._recordToPresentationRecord.put(record, formattedRecord);
    117 
    118         formattedRecord._collapsed = parentRecord === this._rootRecord;
    119         if (coalescingBucket)
    120             this._coalescingBuckets[coalescingBucket] = formattedRecord;
    121 
    122         for (var i = 0; record.children() && i < record.children().length; ++i)
    123             this._innerAddRecord(formattedRecord, record.children()[i]);
    124 
    125         if (parentRecord.coalesced())
    126             this._updateCoalescingParent(formattedRecord);
    127     },
    128 
    129     /**
    130      * @param {!WebInspector.TimelineModel.Record} record
    131      * @param {!WebInspector.TimelinePresentationModel.Record} newParent
    132      * @param {string=} bucket
    133      * @return {?WebInspector.TimelinePresentationModel.Record}
    134      */
    135     _findCoalescedParent: function(record, newParent, bucket)
    136     {
    137         const coalescingThresholdMillis = 5;
    138 
    139         var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent._presentationChildren.peekLast();
    140         if (lastRecord && lastRecord.coalesced())
    141             lastRecord = lastRecord._presentationChildren.peekLast();
    142         var startTime = record.startTime();
    143         var endTime = record.endTime();
    144         if (!lastRecord)
    145             return null;
    146         if (lastRecord.record().type() !== record.type())
    147             return null;
    148         if (!this._uiUtils.isCoalescable(record.type()))
    149             return null;
    150         if (lastRecord.record().endTime() + coalescingThresholdMillis < startTime)
    151             return null;
    152         if (endTime + coalescingThresholdMillis < lastRecord.record().startTime())
    153             return null;
    154         if (lastRecord.presentationParent().coalesced())
    155             return lastRecord.presentationParent();
    156         return this._replaceWithCoalescedRecord(lastRecord);
    157     },
    158 
    159     /**
    160      * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
    161      * @return {!WebInspector.TimelinePresentationModel.Record}
    162      */
    163     _replaceWithCoalescedRecord: function(presentationRecord)
    164     {
    165         var record = presentationRecord.record();
    166         var parent = presentationRecord._presentationParent;
    167         var coalescedRecord = new WebInspector.TimelinePresentationModel.CoalescedRecord(record);
    168 
    169         coalescedRecord._collapsed = true;
    170         coalescedRecord._presentationChildren.push(presentationRecord);
    171         presentationRecord._presentationParent = coalescedRecord;
    172         if (presentationRecord.hasWarnings() || presentationRecord.childHasWarnings())
    173             coalescedRecord._childHasWarnings = true;
    174 
    175         coalescedRecord._presentationParent = parent;
    176         parent._presentationChildren[parent._presentationChildren.indexOf(presentationRecord)] = coalescedRecord;
    177         WebInspector.TimelineUIUtils.aggregateTimeByCategory(coalescedRecord.presentationAggregatedStats(), presentationRecord.presentationAggregatedStats());
    178 
    179         return coalescedRecord;
    180     },
    181 
    182     /**
    183      * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
    184      */
    185     _updateCoalescingParent: function(presentationRecord)
    186     {
    187         var parentRecord = presentationRecord._presentationParent;
    188         WebInspector.TimelineUIUtils.aggregateTimeByCategory(parentRecord.presentationAggregatedStats(), presentationRecord.presentationAggregatedStats());
    189         if (parentRecord.endTime() < presentationRecord.endTime())
    190             parentRecord._endTime = presentationRecord.endTime();
    191     },
    192 
    193     /**
    194      * @param {?RegExp} textFilter
    195      */
    196     setTextFilter: function(textFilter)
    197     {
    198         this._textFilter = textFilter;
    199     },
    200 
    201     invalidateFilteredRecords: function()
    202     {
    203         delete this._filteredRecords;
    204     },
    205 
    206     /**
    207      * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
    208      */
    209     filteredRecords: function()
    210     {
    211         if (this._filteredRecords)
    212             return this._filteredRecords;
    213 
    214         var recordsInWindow = [];
    215 
    216         var stack = [{children: this._rootRecord._presentationChildren, index: 0, parentIsCollapsed: false, parentRecord: {}}];
    217         var revealedDepth = 0;
    218 
    219         function revealRecordsInStack() {
    220             for (var depth = revealedDepth + 1; depth < stack.length; ++depth) {
    221                 if (stack[depth - 1].parentIsCollapsed) {
    222                     stack[depth].parentRecord._presentationParent._expandable = true;
    223                     return;
    224                 }
    225                 stack[depth - 1].parentRecord._collapsed = false;
    226                 recordsInWindow.push(stack[depth].parentRecord);
    227                 stack[depth].windowLengthBeforeChildrenTraversal = recordsInWindow.length;
    228                 stack[depth].parentIsRevealed = true;
    229                 revealedDepth = depth;
    230             }
    231         }
    232 
    233         while (stack.length) {
    234             var entry = stack[stack.length - 1];
    235             var records = entry.children;
    236             if (records && entry.index < records.length) {
    237                 var record = records[entry.index];
    238                 ++entry.index;
    239                 if (record.startTime() < this._windowEndTime && record.endTime() > this._windowStartTime) {
    240                     if (this._model.isVisible(record.record())) {
    241                         record._presentationParent._expandable = true;
    242                         if (this._textFilter)
    243                             revealRecordsInStack();
    244                         if (!entry.parentIsCollapsed) {
    245                             recordsInWindow.push(record);
    246                             revealedDepth = stack.length;
    247                             entry.parentRecord._collapsed = false;
    248                         }
    249                     }
    250                 }
    251 
    252                 record._expandable = false;
    253 
    254                 stack.push({children: record._presentationChildren,
    255                             index: 0,
    256                             parentIsCollapsed: entry.parentIsCollapsed || (record._collapsed && (!this._textFilter || record._expandedOrCollapsedWhileFiltered)),
    257                             parentRecord: record,
    258                             windowLengthBeforeChildrenTraversal: recordsInWindow.length});
    259             } else {
    260                 stack.pop();
    261                 revealedDepth = Math.min(revealedDepth, stack.length - 1);
    262                 entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal;
    263             }
    264         }
    265 
    266         this._filteredRecords = recordsInWindow;
    267         return recordsInWindow;
    268     },
    269 
    270     __proto__: WebInspector.Object.prototype
    271 }
    272 
    273 /**
    274  * @constructor
    275  * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
    276  */
    277 WebInspector.TimelinePresentationModel.Record = function(parentRecord)
    278 {
    279     /**
    280      * @type {!Array.<!WebInspector.TimelinePresentationModel.Record>}
    281      */
    282     this._presentationChildren = [];
    283 
    284     if (parentRecord) {
    285         this._presentationParent = parentRecord;
    286         parentRecord._presentationChildren.push(this);
    287     }
    288 }
    289 
    290 WebInspector.TimelinePresentationModel.Record.prototype = {
    291     /**
    292      * @return {number}
    293      */
    294     startTime: function()
    295     {
    296         throw new Error("Not implemented.");
    297     },
    298 
    299     /**
    300      * @return {number}
    301      */
    302     endTime: function()
    303     {
    304         throw new Error("Not implemented.");
    305     },
    306 
    307     /**
    308      * @return {number}
    309      */
    310     selfTime: function()
    311     {
    312         throw new Error("Not implemented.");
    313     },
    314 
    315     /**
    316      * @return {!WebInspector.TimelineModel.Record}
    317      */
    318     record: function()
    319     {
    320         throw new Error("Not implemented.");
    321     },
    322 
    323     /**
    324      * @return {!Object.<string, number>}
    325      */
    326     presentationAggregatedStats: function()
    327     {
    328         throw new Error("Not implemented.");
    329     },
    330 
    331     /**
    332      * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
    333      */
    334     presentationChildren: function()
    335     {
    336         return this._presentationChildren;
    337     },
    338 
    339     /**
    340      * @return {boolean}
    341      */
    342     coalesced: function()
    343     {
    344         return false;
    345     },
    346 
    347     /**
    348      * @return {boolean}
    349      */
    350     collapsed: function()
    351     {
    352         return this._collapsed;
    353     },
    354 
    355     /**
    356      * @param {boolean} collapsed
    357      */
    358     setCollapsed: function(collapsed)
    359     {
    360         this._collapsed = collapsed;
    361         this._expandedOrCollapsedWhileFiltered = true;
    362     },
    363 
    364     /**
    365      * @return {?WebInspector.TimelinePresentationModel.Record}
    366      */
    367     presentationParent: function()
    368     {
    369         return this._presentationParent || null;
    370     },
    371 
    372     /**
    373      * @return {number}
    374      */
    375     visibleChildrenCount: function()
    376     {
    377         return this._visibleChildrenCount || 0;
    378     },
    379 
    380     /**
    381      * @return {boolean}
    382      */
    383     expandable: function()
    384     {
    385         return !!this._expandable;
    386     },
    387 
    388     /**
    389      * @return {boolean}
    390      */
    391     hasWarnings: function()
    392     {
    393         return false;
    394     },
    395 
    396     /**
    397      * @return {boolean}
    398      */
    399     childHasWarnings: function()
    400     {
    401         return this._childHasWarnings;
    402     },
    403 
    404     /**
    405      * @return {?WebInspector.TimelineRecordListRow}
    406      */
    407     listRow: function()
    408     {
    409         return this._listRow;
    410     },
    411 
    412     /**
    413      * @param {!WebInspector.TimelineRecordListRow} listRow
    414      */
    415     setListRow: function(listRow)
    416     {
    417         this._listRow = listRow;
    418     },
    419 
    420     /**
    421      * @return {?WebInspector.TimelineRecordGraphRow}
    422      */
    423     graphRow: function()
    424     {
    425         return this._graphRow;
    426     },
    427 
    428     /**
    429      * @param {!WebInspector.TimelineRecordGraphRow} graphRow
    430      */
    431     setGraphRow: function(graphRow)
    432     {
    433         this._graphRow = graphRow;
    434     }
    435 }
    436 
    437 /**
    438  * @constructor
    439  * @extends {WebInspector.TimelinePresentationModel.Record}
    440  * @param {!WebInspector.TimelineModel.Record} record
    441  * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
    442  */
    443 WebInspector.TimelinePresentationModel.ActualRecord = function(record, parentRecord)
    444 {
    445     WebInspector.TimelinePresentationModel.Record.call(this, parentRecord);
    446     this._record = record;
    447 
    448     if (this.hasWarnings()) {
    449         for (var parent = this._presentationParent; parent && !parent._childHasWarnings; parent = parent._presentationParent)
    450             parent._childHasWarnings = true;
    451     }
    452 }
    453 
    454 WebInspector.TimelinePresentationModel.ActualRecord.prototype = {
    455     /**
    456      * @return {number}
    457      */
    458     startTime: function()
    459     {
    460         return this._record.startTime();
    461     },
    462 
    463     /**
    464      * @return {number}
    465      */
    466     endTime: function()
    467     {
    468         return this._record.endTime();
    469     },
    470 
    471     /**
    472      * @return {number}
    473      */
    474     selfTime: function()
    475     {
    476         return this._record.selfTime();
    477     },
    478 
    479     /**
    480      * @return {!WebInspector.TimelineModel.Record}
    481      */
    482     record: function()
    483     {
    484         return this._record;
    485     },
    486 
    487     /**
    488      * @return {!Object.<string, number>}
    489      */
    490     presentationAggregatedStats: function()
    491     {
    492         return this._record.aggregatedStats();
    493     },
    494 
    495     /**
    496      * @return {boolean}
    497      */
    498     hasWarnings: function()
    499     {
    500         return !!this._record.warnings();
    501     },
    502 
    503     __proto__: WebInspector.TimelinePresentationModel.Record.prototype
    504 }
    505 
    506 /**
    507  * @constructor
    508  * @extends {WebInspector.TimelinePresentationModel.Record}
    509  * @param {!WebInspector.TimelineModel.Record} record
    510  */
    511 WebInspector.TimelinePresentationModel.CoalescedRecord = function(record)
    512 {
    513     WebInspector.TimelinePresentationModel.Record.call(this, null);
    514     this._startTime = record.startTime();
    515     this._endTime = record.endTime();
    516     this._aggregatedStats = {};
    517 }
    518 
    519 WebInspector.TimelinePresentationModel.CoalescedRecord.prototype = {
    520     /**
    521      * @return {number}
    522      */
    523     startTime: function()
    524     {
    525         return this._startTime;
    526     },
    527 
    528     /**
    529      * @return {number}
    530      */
    531     endTime: function()
    532     {
    533         return this._endTime;
    534     },
    535 
    536     /**
    537      * @return {number}
    538      */
    539     selfTime: function()
    540     {
    541         return 0;
    542     },
    543 
    544     /**
    545      * @return {!WebInspector.TimelineModel.Record}
    546      */
    547     record: function()
    548     {
    549         return this._presentationChildren[0].record();
    550     },
    551 
    552     /**
    553      * @return {!Object.<string, number>}
    554      */
    555     presentationAggregatedStats: function()
    556     {
    557         return this._aggregatedStats;
    558     },
    559 
    560     /**
    561      * @return {boolean}
    562      */
    563     coalesced: function()
    564     {
    565         return true;
    566     },
    567 
    568     /**
    569      * @return {boolean}
    570      */
    571     hasWarnings: function()
    572     {
    573         return false;
    574     },
    575 
    576     __proto__: WebInspector.TimelinePresentationModel.Record.prototype
    577 }
    578 
    579 /**
    580  * @constructor
    581  * @extends {WebInspector.TimelinePresentationModel.Record}
    582  */
    583 WebInspector.TimelinePresentationModel.RootRecord = function()
    584 {
    585     WebInspector.TimelinePresentationModel.Record.call(this, null);
    586     this._aggregatedStats = {};
    587 }
    588 
    589 WebInspector.TimelinePresentationModel.RootRecord.prototype = {
    590     /**
    591      * @return {!Object.<string, number>}
    592      */
    593     presentationAggregatedStats: function()
    594     {
    595         return this._aggregatedStats;
    596     },
    597 
    598     /**
    599      * @return {boolean}
    600      */
    601     hasWarnings: function()
    602     {
    603         return false;
    604     },
    605 
    606     __proto__: WebInspector.TimelinePresentationModel.Record.prototype
    607 }
    608