Home | History | Annotate | Download | only in timeline
      1 /*
      2  * Copyright (C) 2012 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.Object}
     34  */
     35 WebInspector.TimelineModel = function()
     36 {
     37     WebInspector.Object.call(this);
     38     this._filters = [];
     39 }
     40 
     41 WebInspector.TimelineModel.RecordType = {
     42     Root: "Root",
     43     Program: "Program",
     44     EventDispatch: "EventDispatch",
     45 
     46     GPUTask: "GPUTask",
     47 
     48     RequestMainThreadFrame: "RequestMainThreadFrame",
     49     BeginFrame: "BeginFrame",
     50     ActivateLayerTree: "ActivateLayerTree",
     51     DrawFrame: "DrawFrame",
     52     ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
     53     RecalculateStyles: "RecalculateStyles",
     54     InvalidateLayout: "InvalidateLayout",
     55     Layout: "Layout",
     56     UpdateLayerTree: "UpdateLayerTree",
     57     PaintSetup: "PaintSetup",
     58     Paint: "Paint",
     59     Rasterize: "Rasterize",
     60     ScrollLayer: "ScrollLayer",
     61     DecodeImage: "DecodeImage",
     62     ResizeImage: "ResizeImage",
     63     CompositeLayers: "CompositeLayers",
     64 
     65     ParseHTML: "ParseHTML",
     66 
     67     TimerInstall: "TimerInstall",
     68     TimerRemove: "TimerRemove",
     69     TimerFire: "TimerFire",
     70 
     71     XHRReadyStateChange: "XHRReadyStateChange",
     72     XHRLoad: "XHRLoad",
     73     EvaluateScript: "EvaluateScript",
     74 
     75     MarkLoad: "MarkLoad",
     76     MarkDOMContent: "MarkDOMContent",
     77     MarkFirstPaint: "MarkFirstPaint",
     78 
     79     TimeStamp: "TimeStamp",
     80     ConsoleTime: "ConsoleTime",
     81 
     82     ResourceSendRequest: "ResourceSendRequest",
     83     ResourceReceiveResponse: "ResourceReceiveResponse",
     84     ResourceReceivedData: "ResourceReceivedData",
     85     ResourceFinish: "ResourceFinish",
     86 
     87     FunctionCall: "FunctionCall",
     88     GCEvent: "GCEvent",
     89     JSFrame: "JSFrame",
     90 
     91     UpdateCounters: "UpdateCounters",
     92 
     93     RequestAnimationFrame: "RequestAnimationFrame",
     94     CancelAnimationFrame: "CancelAnimationFrame",
     95     FireAnimationFrame: "FireAnimationFrame",
     96 
     97     WebSocketCreate : "WebSocketCreate",
     98     WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
     99     WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
    100     WebSocketDestroy : "WebSocketDestroy",
    101 
    102     EmbedderCallback : "EmbedderCallback",
    103 }
    104 
    105 WebInspector.TimelineModel.Events = {
    106     RecordAdded: "RecordAdded",
    107     RecordsCleared: "RecordsCleared",
    108     RecordingStarted: "RecordingStarted",
    109     RecordingStopped: "RecordingStopped",
    110     RecordingProgress: "RecordingProgress",
    111     RecordFilterChanged: "RecordFilterChanged"
    112 }
    113 
    114 WebInspector.TimelineModel.MainThreadName = "main";
    115 
    116 /**
    117  * @param {!Array.<!WebInspector.TimelineModel.Record>} recordsArray
    118  * @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
    119  * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
    120  * @return {boolean}
    121  */
    122 WebInspector.TimelineModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback)
    123 {
    124     /**
    125      * @param {!Array.<!WebInspector.TimelineModel.Record>} records
    126      * @param {number} depth
    127      * @return {boolean}
    128      */
    129     function processRecords(records, depth)
    130     {
    131         for (var i = 0; i < records.length; ++i) {
    132             var record = records[i];
    133             if (preOrderCallback && preOrderCallback(record, depth))
    134                 return true;
    135             if (processRecords(record.children(), depth + 1))
    136                 return true;
    137             if (postOrderCallback && postOrderCallback(record, depth))
    138                 return true;
    139         }
    140         return false;
    141     }
    142     return processRecords(recordsArray, 0);
    143 }
    144 
    145 WebInspector.TimelineModel.prototype = {
    146     /**
    147      * @param {boolean} captureStacks
    148      * @param {boolean} captureMemory
    149      * @param {boolean} capturePictures
    150      */
    151     startRecording: function(captureStacks, captureMemory, capturePictures)
    152     {
    153     },
    154 
    155     stopRecording: function()
    156     {
    157     },
    158 
    159     /**
    160      * @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
    161      * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
    162      */
    163     forAllRecords: function(preOrderCallback, postOrderCallback)
    164     {
    165         WebInspector.TimelineModel.forAllRecords(this._records, preOrderCallback, postOrderCallback);
    166     },
    167 
    168     /**
    169      * @param {!WebInspector.TimelineModel.Filter} filter
    170      */
    171     addFilter: function(filter)
    172     {
    173         this._filters.push(filter);
    174         filter._model = this;
    175     },
    176 
    177     /**
    178      * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)} callback
    179      */
    180     forAllFilteredRecords: function(callback)
    181     {
    182         /**
    183          * @param {!WebInspector.TimelineModel.Record} record
    184          * @param {number} depth
    185          * @this {WebInspector.TimelineModel}
    186          * @return {boolean}
    187          */
    188         function processRecord(record, depth)
    189         {
    190             var visible = this.isVisible(record);
    191             if (visible) {
    192                 if (callback(record, depth))
    193                     return true;
    194             }
    195 
    196             for (var i = 0; i < record.children().length; ++i) {
    197                 if (processRecord.call(this, record.children()[i], visible ? depth + 1 : depth))
    198                     return true;
    199             }
    200             return false;
    201         }
    202 
    203         for (var i = 0; i < this._records.length; ++i)
    204             processRecord.call(this, this._records[i], 0);
    205     },
    206 
    207     /**
    208      * @param {!WebInspector.TimelineModel.Record} record
    209      * @return {boolean}
    210      */
    211     isVisible: function(record)
    212     {
    213         for (var i = 0; i < this._filters.length; ++i) {
    214             if (!this._filters[i].accept(record))
    215                 return false;
    216         }
    217         return true;
    218     },
    219 
    220     _filterChanged: function()
    221     {
    222         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordFilterChanged);
    223     },
    224 
    225     /**
    226      * @return {!Array.<!WebInspector.TimelineModel.Record>}
    227      */
    228     records: function()
    229     {
    230         return this._records;
    231     },
    232 
    233     /**
    234      * @param {!Blob} file
    235      * @param {!WebInspector.Progress} progress
    236      */
    237     loadFromFile: function(file, progress)
    238     {
    239         var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
    240         var fileReader = this._createFileReader(file, delegate);
    241         var loader = this.createLoader(fileReader, progress);
    242         fileReader.start(loader);
    243     },
    244 
    245     /**
    246      * @param {!WebInspector.ChunkedFileReader} fileReader
    247      * @param {!WebInspector.Progress} progress
    248      * @return {!WebInspector.OutputStream}
    249      */
    250     createLoader: function(fileReader, progress)
    251     {
    252         throw new Error("Not implemented.");
    253     },
    254 
    255     _createFileReader: function(file, delegate)
    256     {
    257         return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModelImpl.TransferChunkLengthBytes, delegate);
    258     },
    259 
    260     _createFileWriter: function()
    261     {
    262         return new WebInspector.FileOutputStream();
    263     },
    264 
    265     saveToFile: function()
    266     {
    267         var now = new Date();
    268         var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
    269         var stream = this._createFileWriter();
    270 
    271         /**
    272          * @param {boolean} accepted
    273          * @this {WebInspector.TimelineModel}
    274          */
    275         function callback(accepted)
    276         {
    277             if (!accepted)
    278                 return;
    279             this.writeToStream(stream);
    280         }
    281         stream.open(fileName, callback.bind(this));
    282     },
    283 
    284     /**
    285      * @param {!WebInspector.OutputStream} stream
    286      */
    287     writeToStream: function(stream)
    288     {
    289         throw new Error("Not implemented.");
    290     },
    291 
    292     reset: function()
    293     {
    294         this._records = [];
    295         this._minimumRecordTime = 0;
    296         this._maximumRecordTime = 0;
    297         /** @type {!Array.<!WebInspector.TimelineModel.Record>} */
    298         this._mainThreadTasks =  [];
    299         /** @type {!Array.<!WebInspector.TimelineModel.Record>} */
    300         this._gpuThreadTasks = [];
    301         /** @type {!Array.<!WebInspector.TimelineModel.Record>} */
    302         this._eventDividerRecords = [];
    303         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
    304     },
    305 
    306     /**
    307      * @return {number}
    308      */
    309     minimumRecordTime: function()
    310     {
    311         return this._minimumRecordTime;
    312     },
    313 
    314     /**
    315      * @return {number}
    316      */
    317     maximumRecordTime: function()
    318     {
    319         return this._maximumRecordTime;
    320     },
    321 
    322     /**
    323      * @return {boolean}
    324      */
    325     isEmpty: function()
    326     {
    327         return this.minimumRecordTime() === 0 && this.maximumRecordTime() === 0;
    328     },
    329 
    330     /**
    331      * @param {!WebInspector.TimelineModel.Record} record
    332      */
    333     _updateBoundaries: function(record)
    334     {
    335         var startTime = record.startTime();
    336         var endTime = record.endTime();
    337 
    338         if (!this._minimumRecordTime || startTime < this._minimumRecordTime)
    339             this._minimumRecordTime = startTime;
    340         if (endTime > this._maximumRecordTime)
    341             this._maximumRecordTime = endTime;
    342     },
    343 
    344     /**
    345      * @return {!Array.<!WebInspector.TimelineModel.Record>}
    346      */
    347     mainThreadTasks: function()
    348     {
    349         return this._mainThreadTasks;
    350     },
    351 
    352     /**
    353      * @return {!Array.<!WebInspector.TimelineModel.Record>}
    354      */
    355     gpuThreadTasks: function()
    356     {
    357         return this._gpuThreadTasks;
    358     },
    359 
    360     /**
    361      * @return {!Array.<!WebInspector.TimelineModel.Record>}
    362      */
    363     eventDividerRecords: function()
    364     {
    365         return this._eventDividerRecords;
    366     },
    367 
    368     __proto__: WebInspector.Object.prototype
    369 }
    370 
    371 /**
    372  * @interface
    373  */
    374 WebInspector.TimelineModel.Record = function()
    375 {
    376 }
    377 
    378 WebInspector.TimelineModel.Record.prototype = {
    379     /**
    380      * @return {?Array.<!ConsoleAgent.CallFrame>}
    381      */
    382     callSiteStackTrace: function() { },
    383 
    384     /**
    385      * @return {?WebInspector.TimelineModel.Record}
    386      */
    387     initiator: function() { },
    388 
    389     /**
    390      * @return {?WebInspector.Target}
    391      */
    392     target: function() { },
    393 
    394     /**
    395      * @return {number}
    396      */
    397     selfTime: function() { },
    398 
    399     /**
    400      * @return {!Array.<!WebInspector.TimelineModel.Record>}
    401      */
    402     children: function() { },
    403 
    404     /**
    405      * @return {number}
    406      */
    407     startTime: function() { },
    408 
    409     /**
    410      * @return {string}
    411      */
    412     thread: function() { },
    413 
    414     /**
    415      * @return {number}
    416      */
    417     endTime: function() { },
    418 
    419     /**
    420      * @param {number} endTime
    421      */
    422     setEndTime: function(endTime) { },
    423 
    424     /**
    425      * @return {!Object}
    426      */
    427     data: function() { },
    428 
    429     /**
    430      * @return {string}
    431      */
    432     type: function() { },
    433 
    434     /**
    435      * @return {string}
    436      */
    437     frameId: function() { },
    438 
    439     /**
    440      * @return {?Array.<!ConsoleAgent.CallFrame>}
    441      */
    442     stackTrace: function() { },
    443 
    444     /**
    445      * @param {string} key
    446      * @return {?Object}
    447      */
    448     getUserObject: function(key) { },
    449 
    450     /**
    451      * @param {string} key
    452      * @param {?Object|undefined} value
    453      */
    454     setUserObject: function(key, value) { },
    455 
    456     /**
    457      * @return {?Array.<string>}
    458      */
    459     warnings: function() { }
    460 }
    461 
    462 /**
    463  * @constructor
    464  */
    465 WebInspector.TimelineModel.Filter = function()
    466 {
    467     /** @type {!WebInspector.TimelineModel} */
    468     this._model;
    469 }
    470 
    471 WebInspector.TimelineModel.Filter.prototype = {
    472     /**
    473      * @param {!WebInspector.TimelineModel.Record} record
    474      * @return {boolean}
    475      */
    476     accept: function(record)
    477     {
    478         return true;
    479     },
    480 
    481     notifyFilterChanged: function()
    482     {
    483         this._model._filterChanged();
    484     }
    485 }
    486 
    487 /**
    488  * @constructor
    489  * @extends {WebInspector.TimelineModel.Filter}
    490  * @param {!Array.<string>} recordTypes
    491  */
    492 WebInspector.TimelineRecordTypeFilter = function(recordTypes)
    493 {
    494     WebInspector.TimelineModel.Filter.call(this);
    495     this._recordTypes = recordTypes.keySet();
    496 }
    497 
    498 WebInspector.TimelineRecordTypeFilter.prototype = {
    499     __proto__: WebInspector.TimelineModel.Filter.prototype
    500 }
    501 
    502 /**
    503  * @constructor
    504  * @extends {WebInspector.TimelineRecordTypeFilter}
    505  * @param {!Array.<string>} recordTypes
    506  */
    507 WebInspector.TimelineRecordHiddenEmptyTypeFilter = function(recordTypes)
    508 {
    509     WebInspector.TimelineRecordTypeFilter.call(this, recordTypes);
    510 }
    511 
    512 WebInspector.TimelineRecordHiddenEmptyTypeFilter.prototype = {
    513     /**
    514      * @param {!WebInspector.TimelineModel.Record} record
    515      * @return {boolean}
    516      */
    517     accept: function(record)
    518     {
    519         return record.children().length !== 0 || !this._recordTypes[record.type()];
    520     },
    521 
    522     __proto__: WebInspector.TimelineRecordTypeFilter.prototype
    523 }
    524 
    525 /**
    526  * @constructor
    527  * @extends {WebInspector.TimelineRecordTypeFilter}
    528  * @param {!Array.<string>} recordTypes
    529  */
    530 WebInspector.TimelineRecordHiddenTypeFilter = function(recordTypes)
    531 {
    532     WebInspector.TimelineRecordTypeFilter.call(this, recordTypes);
    533 }
    534 
    535 WebInspector.TimelineRecordHiddenTypeFilter.prototype = {
    536     /**
    537      * @param {!WebInspector.TimelineModel.Record} record
    538      * @return {boolean}
    539      */
    540     accept: function(record)
    541     {
    542         return !this._recordTypes[record.type()];
    543     },
    544 
    545     __proto__: WebInspector.TimelineRecordTypeFilter.prototype
    546 }
    547 
    548 /**
    549  * @constructor
    550  * @extends {WebInspector.TimelineRecordTypeFilter}
    551  * @param {!Array.<string>} recordTypes
    552  */
    553 WebInspector.TimelineRecordVisibleTypeFilter = function(recordTypes)
    554 {
    555     WebInspector.TimelineRecordTypeFilter.call(this, recordTypes);
    556 }
    557 
    558 WebInspector.TimelineRecordVisibleTypeFilter.prototype = {
    559     /**
    560      * @param {!WebInspector.TimelineModel.Record} record
    561      * @return {boolean}
    562      */
    563     accept: function(record)
    564     {
    565         return !!this._recordTypes[record.type()];
    566     },
    567 
    568     __proto__: WebInspector.TimelineRecordTypeFilter.prototype
    569 }
    570 
    571 /**
    572  * @constructor
    573  */
    574 WebInspector.TimelineMergingRecordBuffer = function()
    575 {
    576     this._backgroundRecordsBuffer = [];
    577 }
    578 
    579 /**
    580  * @constructor
    581  */
    582 WebInspector.TimelineMergingRecordBuffer.prototype = {
    583     /**
    584      * @param {string} thread
    585      * @param {!Array.<!WebInspector.TimelineModel.Record>} records
    586      * @return {!Array.<!WebInspector.TimelineModel.Record>}
    587      */
    588     process: function(thread, records)
    589     {
    590         if (thread !== WebInspector.TimelineModel.MainThreadName) {
    591             this._backgroundRecordsBuffer = this._backgroundRecordsBuffer.concat(records);
    592             return [];
    593         }
    594         /**
    595          * @param {!WebInspector.TimelineModel.Record} a
    596          * @param {!WebInspector.TimelineModel.Record} b
    597          */
    598         function recordTimestampComparator(a, b)
    599         {
    600             // Never return 0, as the merge function will squash identical entries.
    601             return a.startTime() < b.startTime() ? -1 : 1;
    602         }
    603         var result = this._backgroundRecordsBuffer.mergeOrdered(records, recordTimestampComparator);
    604         this._backgroundRecordsBuffer = [];
    605         return result;
    606     }
    607 }
    608 
    609 /**
    610  * @constructor
    611  * @implements {WebInspector.OutputStreamDelegate}
    612  * @param {!WebInspector.TimelineModel} model
    613  * @param {!WebInspector.Progress} progress
    614  */
    615 WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress)
    616 {
    617     this._model = model;
    618     this._progress = progress;
    619 }
    620 
    621 WebInspector.TimelineModelLoadFromFileDelegate.prototype = {
    622     onTransferStarted: function()
    623     {
    624         this._progress.setTitle(WebInspector.UIString("Loading\u2026"));
    625     },
    626 
    627     /**
    628      * @param {!WebInspector.ChunkedReader} reader
    629      */
    630     onChunkTransferred: function(reader)
    631     {
    632         if (this._progress.isCanceled()) {
    633             reader.cancel();
    634             this._progress.done();
    635             this._model.reset();
    636             return;
    637         }
    638 
    639         var totalSize = reader.fileSize();
    640         if (totalSize) {
    641             this._progress.setTotalWork(totalSize);
    642             this._progress.setWorked(reader.loadedSize());
    643         }
    644     },
    645 
    646     onTransferFinished: function()
    647     {
    648         this._progress.done();
    649     },
    650 
    651     /**
    652      * @param {!WebInspector.ChunkedReader} reader
    653      * @param {!Event} event
    654      */
    655     onError: function(reader, event)
    656     {
    657         this._progress.done();
    658         this._model.reset();
    659         switch (event.target.error.code) {
    660         case FileError.NOT_FOUND_ERR:
    661             WebInspector.console.error(WebInspector.UIString("File \"%s\" not found.", reader.fileName()));
    662             break;
    663         case FileError.NOT_READABLE_ERR:
    664             WebInspector.console.error(WebInspector.UIString("File \"%s\" is not readable", reader.fileName()));
    665             break;
    666         case FileError.ABORT_ERR:
    667             break;
    668         default:
    669             WebInspector.console.error(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName()));
    670         }
    671     }
    672 }
    673