Home | History | Annotate | Download | only in front_end
      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     this._records = [];
     38     this._stringPool = new StringPool();
     39     this._minimumRecordTime = -1;
     40     this._maximumRecordTime = -1;
     41     this._collectionEnabled = false;
     42 
     43     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this);
     44     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStartEvent, this._onTimelineStarted, this);
     45 }
     46 
     47 WebInspector.TimelineModel.TransferChunkLengthBytes = 5000000;
     48 
     49 WebInspector.TimelineModel.RecordType = {
     50     Root: "Root",
     51     Program: "Program",
     52     EventDispatch: "EventDispatch",
     53 
     54     BeginFrame: "BeginFrame",
     55     ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
     56     RecalculateStyles: "RecalculateStyles",
     57     InvalidateLayout: "InvalidateLayout",
     58     Layout: "Layout",
     59     PaintSetup: "PaintSetup",
     60     Paint: "Paint",
     61     Rasterize: "Rasterize",
     62     ScrollLayer: "ScrollLayer",
     63     DecodeImage: "DecodeImage",
     64     ResizeImage: "ResizeImage",
     65     CompositeLayers: "CompositeLayers",
     66 
     67     ParseHTML: "ParseHTML",
     68 
     69     TimerInstall: "TimerInstall",
     70     TimerRemove: "TimerRemove",
     71     TimerFire: "TimerFire",
     72 
     73     XHRReadyStateChange: "XHRReadyStateChange",
     74     XHRLoad: "XHRLoad",
     75     EvaluateScript: "EvaluateScript",
     76 
     77     MarkLoad: "MarkLoad",
     78     MarkDOMContent: "MarkDOMContent",
     79 
     80     TimeStamp: "TimeStamp",
     81     Time: "Time",
     82     TimeEnd: "TimeEnd",
     83 
     84     ScheduleResourceRequest: "ScheduleResourceRequest",
     85     ResourceSendRequest: "ResourceSendRequest",
     86     ResourceReceiveResponse: "ResourceReceiveResponse",
     87     ResourceReceivedData: "ResourceReceivedData",
     88     ResourceFinish: "ResourceFinish",
     89 
     90     FunctionCall: "FunctionCall",
     91     GCEvent: "GCEvent",
     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 
    103 WebInspector.TimelineModel.Events = {
    104     RecordAdded: "RecordAdded",
    105     RecordsCleared: "RecordsCleared"
    106 }
    107 
    108 WebInspector.TimelineModel.startTimeInSeconds = function(record)
    109 {
    110     return record.startTime / 1000;
    111 }
    112 
    113 WebInspector.TimelineModel.endTimeInSeconds = function(record)
    114 {
    115     return (typeof record.endTime === "undefined" ? record.startTime : record.endTime) / 1000;
    116 }
    117 
    118 WebInspector.TimelineModel.durationInSeconds = function(record)
    119 {
    120     return WebInspector.TimelineModel.endTimeInSeconds(record) - WebInspector.TimelineModel.startTimeInSeconds(record);
    121 }
    122 
    123 /**
    124  * @param {Object} total
    125  * @param {Object} rawRecord
    126  */
    127 WebInspector.TimelineModel.aggregateTimeForRecord = function(total, rawRecord)
    128 {
    129     var childrenTime = 0;
    130     var children = rawRecord["children"] || [];
    131     for (var i = 0; i < children.length; ++i) {
    132         WebInspector.TimelineModel.aggregateTimeForRecord(total, children[i]);
    133         childrenTime += WebInspector.TimelineModel.durationInSeconds(children[i]);
    134     }
    135     var categoryName = WebInspector.TimelinePresentationModel.recordStyle(rawRecord).category.name;
    136     var ownTime = WebInspector.TimelineModel.durationInSeconds(rawRecord) - childrenTime;
    137     total[categoryName] = (total[categoryName] || 0) + ownTime;
    138 }
    139 
    140 /**
    141  * @param {Object} total
    142  * @param {Object} addend
    143  */
    144 WebInspector.TimelineModel.aggregateTimeByCategory = function(total, addend)
    145 {
    146     for (var category in addend)
    147         total[category] = (total[category] || 0) + addend[category];
    148 }
    149 
    150 WebInspector.TimelineModel.prototype = {
    151     /**
    152      * @param {boolean=} includeDomCounters
    153      */
    154     startRecord: function(includeDomCounters)
    155     {
    156         if (this._collectionEnabled)
    157             return;
    158         this.reset();
    159         var maxStackFrames = WebInspector.settings.timelineLimitStackFramesFlag.get() ? WebInspector.settings.timelineStackFramesToCapture.get() : 30;
    160         WebInspector.timelineManager.start(maxStackFrames, includeDomCounters);
    161         this._collectionEnabled = true;
    162     },
    163 
    164     stopRecord: function()
    165     {
    166         if (!this._collectionEnabled)
    167             return;
    168         WebInspector.timelineManager.stop();
    169         this._collectionEnabled = false;
    170     },
    171 
    172     get records()
    173     {
    174         return this._records;
    175     },
    176 
    177     _onRecordAdded: function(event)
    178     {
    179         if (this._collectionEnabled)
    180             this._addRecord(event.data);
    181     },
    182 
    183     _addRecord: function(record)
    184     {
    185         this._stringPool.internObjectStrings(record);
    186         this._records.push(record);
    187         this._updateBoundaries(record);
    188         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
    189     },
    190 
    191     /**
    192      * @param {!Blob} file
    193      * @param {!WebInspector.Progress} progress
    194      */
    195     loadFromFile: function(file, progress)
    196     {
    197         var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
    198         var fileReader = this._createFileReader(file, delegate);
    199         var loader = new WebInspector.TimelineModelLoader(this, fileReader, progress);
    200         fileReader.start(loader);
    201     },
    202 
    203     /**
    204      * @param {string} url
    205      */
    206     loadFromURL: function(url, progress)
    207     {
    208         var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
    209         var urlReader = new WebInspector.ChunkedXHRReader(url, delegate);
    210         var loader = new WebInspector.TimelineModelLoader(this, urlReader, progress);
    211         urlReader.start(loader);
    212     },
    213 
    214     _createFileReader: function(file, delegate)
    215     {
    216         return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.TransferChunkLengthBytes, delegate);
    217     },
    218 
    219     _createFileWriter: function(fileName, callback)
    220     {
    221         var stream = new WebInspector.FileOutputStream();
    222         stream.open(fileName, callback);
    223     },
    224 
    225     saveToFile: function()
    226     {
    227         var now = new Date();
    228         var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
    229         function callback(stream)
    230         {
    231             var saver = new WebInspector.TimelineSaver(stream);
    232             saver.save(this._records, window.navigator.appVersion);
    233         }
    234         this._createFileWriter(fileName, callback.bind(this));
    235     },
    236 
    237     reset: function()
    238     {
    239         this._records = [];
    240         this._stringPool.reset();
    241         this._minimumRecordTime = -1;
    242         this._maximumRecordTime = -1;
    243         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
    244     },
    245 
    246     minimumRecordTime: function()
    247     {
    248         return this._minimumRecordTime;
    249     },
    250 
    251     maximumRecordTime: function()
    252     {
    253         return this._maximumRecordTime;
    254     },
    255 
    256     _updateBoundaries: function(record)
    257     {
    258         var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
    259         var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
    260 
    261         if (this._minimumRecordTime === -1 || startTime < this._minimumRecordTime)
    262             this._minimumRecordTime = startTime;
    263         if (this._maximumRecordTime === -1 || endTime > this._maximumRecordTime)
    264             this._maximumRecordTime = endTime;
    265     },
    266 
    267     _onTimelineStarted: function(event)
    268     {
    269         if (event.data.timestampsBase)
    270             this._timestampsBase = event.data.timestampsBase;
    271         if (event.data.startTime)
    272             this._startTime = event.data.startTime;
    273     },
    274 
    275     /**
    276      * @param {Object} rawRecord
    277      */
    278     recordOffsetInSeconds: function(rawRecord)
    279     {
    280         return WebInspector.TimelineModel.startTimeInSeconds(rawRecord) - this._minimumRecordTime;
    281     },
    282 
    283     __proto__: WebInspector.Object.prototype
    284 }
    285 
    286 /**
    287  * @constructor
    288  * @implements {WebInspector.OutputStream}
    289  * @param {!WebInspector.TimelineModel} model
    290  * @param {!{cancel: function()}} reader
    291  * @param {!WebInspector.Progress} progress
    292  */
    293 WebInspector.TimelineModelLoader = function(model, reader, progress)
    294 {
    295     this._model = model;
    296     this._reader = reader;
    297     this._progress = progress;
    298     this._buffer = "";
    299     this._firstChunk = true;
    300 }
    301 
    302 WebInspector.TimelineModelLoader.prototype = {
    303     /**
    304      * @param {string} chunk
    305      */
    306     write: function(chunk)
    307     {
    308         var data = this._buffer + chunk;
    309         var lastIndex = 0;
    310         var index;
    311         do {
    312             index = lastIndex;
    313             lastIndex = WebInspector.findBalancedCurlyBrackets(data, index);
    314         } while (lastIndex !== -1)
    315 
    316         var json = data.slice(0, index) + "]";
    317         this._buffer = data.slice(index);
    318 
    319         if (!index)
    320             return;
    321 
    322         // Prepending "0" to turn string into valid JSON.
    323         if (!this._firstChunk)
    324             json = "[0" + json;
    325 
    326         var items;
    327         try {
    328             items = /** @type {Array} */ (JSON.parse(json));
    329         } catch (e) {
    330             WebInspector.showErrorMessage("Malformed timeline data.");
    331             this._model.reset();
    332             this._reader.cancel();
    333             this._progress.done();
    334             return;
    335         }
    336 
    337         if (this._firstChunk) {
    338             this._version = items[0];
    339             this._firstChunk = false;
    340             this._model.reset();
    341         }
    342 
    343         // Skip 0-th element - it is either version or 0.
    344         for (var i = 1, size = items.length; i < size; ++i)
    345             this._model._addRecord(items[i]);
    346     },
    347 
    348     close: function() { }
    349 }
    350 
    351 /**
    352  * @constructor
    353  * @implements {WebInspector.OutputStreamDelegate}
    354  * @param {!WebInspector.TimelineModel} model
    355  * @param {!WebInspector.Progress} progress
    356  */
    357 WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress)
    358 {
    359     this._model = model;
    360     this._progress = progress;
    361 }
    362 
    363 WebInspector.TimelineModelLoadFromFileDelegate.prototype = {
    364     onTransferStarted: function()
    365     {
    366         this._progress.setTitle(WebInspector.UIString("Loading\u2026"));
    367     },
    368 
    369     /**
    370      * @param {WebInspector.ChunkedReader} reader
    371      */
    372     onChunkTransferred: function(reader)
    373     {
    374         if (this._progress.isCanceled()) {
    375             reader.cancel();
    376             this._progress.done();
    377             this._model.reset();
    378             return;
    379         }
    380 
    381         var totalSize = reader.fileSize();
    382         if (totalSize) {
    383             this._progress.setTotalWork(totalSize);
    384             this._progress.setWorked(reader.loadedSize());
    385         }
    386     },
    387 
    388     onTransferFinished: function()
    389     {
    390         this._progress.done();
    391     },
    392 
    393     /**
    394      * @param {WebInspector.ChunkedReader} reader
    395      */
    396     onError: function(reader, event)
    397     {
    398         this._progress.done();
    399         this._model.reset();
    400         switch (event.target.error.code) {
    401         case FileError.NOT_FOUND_ERR:
    402             WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" not found.", reader.fileName()));
    403             break;
    404         case FileError.NOT_READABLE_ERR:
    405             WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" is not readable", reader.fileName()));
    406             break;
    407         case FileError.ABORT_ERR:
    408             break;
    409         default:
    410             WebInspector.showErrorMessage(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName()));
    411         }
    412     }
    413 }
    414 
    415 /**
    416  * @constructor
    417  */
    418 WebInspector.TimelineSaver = function(stream)
    419 {
    420     this._stream = stream;
    421 }
    422 
    423 WebInspector.TimelineSaver.prototype = {
    424     /**
    425      * @param {Array} records
    426      * @param {string} version
    427      */
    428     save: function(records, version)
    429     {
    430         this._records = records;
    431         this._recordIndex = 0;
    432         this._prologue = "[" + JSON.stringify(version);
    433 
    434         this._writeNextChunk(this._stream);
    435     },
    436 
    437     _writeNextChunk: function(stream)
    438     {
    439         const separator = ",\n";
    440         var data = [];
    441         var length = 0;
    442 
    443         if (this._prologue) {
    444             data.push(this._prologue);
    445             length += this._prologue.length;
    446             delete this._prologue;
    447         } else {
    448             if (this._recordIndex === this._records.length) {
    449                 stream.close();
    450                 return;
    451             }
    452             data.push("");
    453         }
    454         while (this._recordIndex < this._records.length) {
    455             var item = JSON.stringify(this._records[this._recordIndex]);
    456             var itemLength = item.length + separator.length;
    457             if (length + itemLength > WebInspector.TimelineModel.TransferChunkLengthBytes)
    458                 break;
    459             length += itemLength;
    460             data.push(item);
    461             ++this._recordIndex;
    462         }
    463         if (this._recordIndex === this._records.length)
    464             data.push(data.pop() + "]");
    465         stream.write(data.join(separator), this._writeNextChunk.bind(this));
    466     }
    467 }
    468