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.set(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 
    178         return coalescedRecord;
    179     },
    180 
    181     /**
    182      * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
    183      */
    184     _updateCoalescingParent: function(presentationRecord)
    185     {
    186         var parentRecord = presentationRecord._presentationParent;
    187         if (parentRecord.endTime() < presentationRecord.endTime())
    188             parentRecord._endTime = presentationRecord.endTime();
    189     },
    190 
    191     /**
    192      * @param {?RegExp} textFilter
    193      */
    194     setTextFilter: function(textFilter)
    195     {
    196         var records = this._recordToPresentationRecord.values();
    197         for (var i = 0; i < records.length; ++i)
    198             records[i]._expandedOrCollapsedWhileFiltered = false;
    199         this._textFilter = textFilter;
    200     },
    201 
    202     refreshRecords: function()
    203     {
    204         this.reset();
    205         var modelRecords = this._model.records();
    206         for (var i = 0; i < modelRecords.length; ++i)
    207             this.addRecord(modelRecords[i]);
    208     },
    209 
    210     invalidateFilteredRecords: function()
    211     {
    212         delete this._filteredRecords;
    213     },
    214 
    215     /**
    216      * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
    217      */
    218     filteredRecords: function()
    219     {
    220         if (this._filteredRecords)
    221             return this._filteredRecords;
    222 
    223         var recordsInWindow = [];
    224 
    225         var stack = [{children: this._rootRecord._presentationChildren, index: 0, parentIsCollapsed: false, parentRecord: {}}];
    226         var revealedDepth = 0;
    227 
    228         function revealRecordsInStack() {
    229             for (var depth = revealedDepth + 1; depth < stack.length; ++depth) {
    230                 if (stack[depth - 1].parentIsCollapsed) {
    231                     stack[depth].parentRecord._presentationParent._expandable = true;
    232                     return;
    233                 }
    234                 stack[depth - 1].parentRecord._collapsed = false;
    235                 recordsInWindow.push(stack[depth].parentRecord);
    236                 stack[depth].windowLengthBeforeChildrenTraversal = recordsInWindow.length;
    237                 stack[depth].parentIsRevealed = true;
    238                 revealedDepth = depth;
    239             }
    240         }
    241 
    242         while (stack.length) {
    243             var entry = stack[stack.length - 1];
    244             var records = entry.children;
    245             if (records && entry.index < records.length) {
    246                 var record = records[entry.index];
    247                 ++entry.index;
    248                 if (record.startTime() < this._windowEndTime && record.endTime() > this._windowStartTime) {
    249                     if (this._model.isVisible(record.record())) {
    250                         record._presentationParent._expandable = true;
    251                         if (this._textFilter)
    252                             revealRecordsInStack();
    253                         if (!entry.parentIsCollapsed) {
    254                             recordsInWindow.push(record);
    255                             revealedDepth = stack.length;
    256                             entry.parentRecord._collapsed = false;
    257                         }
    258                     }
    259                 }
    260 
    261                 record._expandable = false;
    262 
    263                 stack.push({children: record._presentationChildren,
    264                             index: 0,
    265                             parentIsCollapsed: entry.parentIsCollapsed || (record._collapsed && (!this._textFilter || record._expandedOrCollapsedWhileFiltered)),
    266                             parentRecord: record,
    267                             windowLengthBeforeChildrenTraversal: recordsInWindow.length});
    268             } else {
    269                 stack.pop();
    270                 revealedDepth = Math.min(revealedDepth, stack.length - 1);
    271                 entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal;
    272             }
    273         }
    274 
    275         this._filteredRecords = recordsInWindow;
    276         return recordsInWindow;
    277     },
    278 
    279     __proto__: WebInspector.Object.prototype
    280 }
    281 
    282 /**
    283  * @constructor
    284  * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
    285  */
    286 WebInspector.TimelinePresentationModel.Record = function(parentRecord)
    287 {
    288     /**
    289      * @type {!Array.<!WebInspector.TimelinePresentationModel.Record>}
    290      */
    291     this._presentationChildren = [];
    292 
    293     if (parentRecord) {
    294         this._presentationParent = parentRecord;
    295         parentRecord._presentationChildren.push(this);
    296     }
    297 }
    298 
    299 WebInspector.TimelinePresentationModel.Record.prototype = {
    300     /**
    301      * @return {number}
    302      */
    303     startTime: function()
    304     {
    305         throw new Error("Not implemented.");
    306     },
    307 
    308     /**
    309      * @return {number}
    310      */
    311     endTime: function()
    312     {
    313         throw new Error("Not implemented.");
    314     },
    315 
    316     /**
    317      * @return {number}
    318      */
    319     selfTime: function()
    320     {
    321         throw new Error("Not implemented.");
    322     },
    323 
    324     /**
    325      * @return {!WebInspector.TimelineModel.Record}
    326      */
    327     record: function()
    328     {
    329         throw new Error("Not implemented.");
    330     },
    331 
    332     /**
    333      * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
    334      */
    335     presentationChildren: function()
    336     {
    337         return this._presentationChildren;
    338     },
    339 
    340     /**
    341      * @return {boolean}
    342      */
    343     coalesced: function()
    344     {
    345         return false;
    346     },
    347 
    348     /**
    349      * @return {boolean}
    350      */
    351     collapsed: function()
    352     {
    353         return this._collapsed;
    354     },
    355 
    356     /**
    357      * @param {boolean} collapsed
    358      */
    359     setCollapsed: function(collapsed)
    360     {
    361         this._collapsed = collapsed;
    362         this._expandedOrCollapsedWhileFiltered = true;
    363     },
    364 
    365     /**
    366      * @return {?WebInspector.TimelinePresentationModel.Record}
    367      */
    368     presentationParent: function()
    369     {
    370         return this._presentationParent || null;
    371     },
    372 
    373     /**
    374      * @return {number}
    375      */
    376     visibleChildrenCount: function()
    377     {
    378         return this._visibleChildrenCount || 0;
    379     },
    380 
    381     /**
    382      * @return {boolean}
    383      */
    384     expandable: function()
    385     {
    386         return !!this._expandable;
    387     },
    388 
    389     /**
    390      * @return {boolean}
    391      */
    392     hasWarnings: function()
    393     {
    394         return false;
    395     },
    396 
    397     /**
    398      * @return {boolean}
    399      */
    400     childHasWarnings: function()
    401     {
    402         return this._childHasWarnings;
    403     },
    404 
    405     /**
    406      * @return {?WebInspector.TimelineRecordListRow}
    407      */
    408     listRow: function()
    409     {
    410         return this._listRow;
    411     },
    412 
    413     /**
    414      * @param {!WebInspector.TimelineRecordListRow} listRow
    415      */
    416     setListRow: function(listRow)
    417     {
    418         this._listRow = listRow;
    419     },
    420 
    421     /**
    422      * @return {?WebInspector.TimelineRecordGraphRow}
    423      */
    424     graphRow: function()
    425     {
    426         return this._graphRow;
    427     },
    428 
    429     /**
    430      * @param {!WebInspector.TimelineRecordGraphRow} graphRow
    431      */
    432     setGraphRow: function(graphRow)
    433     {
    434         this._graphRow = graphRow;
    435     }
    436 }
    437 
    438 /**
    439  * @constructor
    440  * @extends {WebInspector.TimelinePresentationModel.Record}
    441  * @param {!WebInspector.TimelineModel.Record} record
    442  * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
    443  */
    444 WebInspector.TimelinePresentationModel.ActualRecord = function(record, parentRecord)
    445 {
    446     WebInspector.TimelinePresentationModel.Record.call(this, parentRecord);
    447     this._record = record;
    448 
    449     if (this.hasWarnings()) {
    450         for (var parent = this._presentationParent; parent && !parent._childHasWarnings; parent = parent._presentationParent)
    451             parent._childHasWarnings = true;
    452     }
    453 }
    454 
    455 WebInspector.TimelinePresentationModel.ActualRecord.prototype = {
    456     /**
    457      * @return {number}
    458      */
    459     startTime: function()
    460     {
    461         return this._record.startTime();
    462     },
    463 
    464     /**
    465      * @return {number}
    466      */
    467     endTime: function()
    468     {
    469         return this._record.endTime();
    470     },
    471 
    472     /**
    473      * @return {number}
    474      */
    475     selfTime: function()
    476     {
    477         return this._record.selfTime();
    478     },
    479 
    480     /**
    481      * @return {!WebInspector.TimelineModel.Record}
    482      */
    483     record: function()
    484     {
    485         return this._record;
    486     },
    487 
    488     /**
    489      * @return {boolean}
    490      */
    491     hasWarnings: function()
    492     {
    493         return !!this._record.warnings();
    494     },
    495 
    496     __proto__: WebInspector.TimelinePresentationModel.Record.prototype
    497 }
    498 
    499 /**
    500  * @constructor
    501  * @extends {WebInspector.TimelinePresentationModel.Record}
    502  * @param {!WebInspector.TimelineModel.Record} record
    503  */
    504 WebInspector.TimelinePresentationModel.CoalescedRecord = function(record)
    505 {
    506     WebInspector.TimelinePresentationModel.Record.call(this, null);
    507     this._startTime = record.startTime();
    508     this._endTime = record.endTime();
    509 }
    510 
    511 WebInspector.TimelinePresentationModel.CoalescedRecord.prototype = {
    512     /**
    513      * @return {number}
    514      */
    515     startTime: function()
    516     {
    517         return this._startTime;
    518     },
    519 
    520     /**
    521      * @return {number}
    522      */
    523     endTime: function()
    524     {
    525         return this._endTime;
    526     },
    527 
    528     /**
    529      * @return {number}
    530      */
    531     selfTime: function()
    532     {
    533         return 0;
    534     },
    535 
    536     /**
    537      * @return {!WebInspector.TimelineModel.Record}
    538      */
    539     record: function()
    540     {
    541         return this._presentationChildren[0].record();
    542     },
    543 
    544     /**
    545      * @return {boolean}
    546      */
    547     coalesced: function()
    548     {
    549         return true;
    550     },
    551 
    552     /**
    553      * @return {boolean}
    554      */
    555     hasWarnings: function()
    556     {
    557         return false;
    558     },
    559 
    560     __proto__: WebInspector.TimelinePresentationModel.Record.prototype
    561 }
    562 
    563 /**
    564  * @constructor
    565  * @extends {WebInspector.TimelinePresentationModel.Record}
    566  */
    567 WebInspector.TimelinePresentationModel.RootRecord = function()
    568 {
    569     WebInspector.TimelinePresentationModel.Record.call(this, null);
    570 }
    571 
    572 WebInspector.TimelinePresentationModel.RootRecord.prototype = {
    573     /**
    574      * @return {boolean}
    575      */
    576     hasWarnings: function()
    577     {
    578         return false;
    579     },
    580 
    581     __proto__: WebInspector.TimelinePresentationModel.Record.prototype
    582 }
    583