Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / TimelineModel.js
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
42     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this);
43     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStarted, this._onStarted, this);
44     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStopped, this._onStopped, this);
45 }
46
47 WebInspector.TimelineModel.TransferChunkLengthBytes = 5000000;
48
49 WebInspector.TimelineModel.RecordType = {
50     Root: "Root",
51     Program: "Program",
52     EventDispatch: "EventDispatch",
53
54     GPUTask: "GPUTask",
55
56     RequestMainThreadFrame: "RequestMainThreadFrame",
57     BeginFrame: "BeginFrame",
58     ActivateLayerTree: "ActivateLayerTree",
59     DrawFrame: "DrawFrame",
60     ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
61     RecalculateStyles: "RecalculateStyles",
62     InvalidateLayout: "InvalidateLayout",
63     Layout: "Layout",
64     AutosizeText: "AutosizeText",
65     PaintSetup: "PaintSetup",
66     Paint: "Paint",
67     Rasterize: "Rasterize",
68     ScrollLayer: "ScrollLayer",
69     DecodeImage: "DecodeImage",
70     ResizeImage: "ResizeImage",
71     CompositeLayers: "CompositeLayers",
72
73     ParseHTML: "ParseHTML",
74
75     TimerInstall: "TimerInstall",
76     TimerRemove: "TimerRemove",
77     TimerFire: "TimerFire",
78
79     XHRReadyStateChange: "XHRReadyStateChange",
80     XHRLoad: "XHRLoad",
81     EvaluateScript: "EvaluateScript",
82
83     MarkLoad: "MarkLoad",
84     MarkDOMContent: "MarkDOMContent",
85     MarkFirstPaint: "MarkFirstPaint",
86
87     TimeStamp: "TimeStamp",
88     Time: "Time",
89     TimeEnd: "TimeEnd",
90
91     ScheduleResourceRequest: "ScheduleResourceRequest",
92     ResourceSendRequest: "ResourceSendRequest",
93     ResourceReceiveResponse: "ResourceReceiveResponse",
94     ResourceReceivedData: "ResourceReceivedData",
95     ResourceFinish: "ResourceFinish",
96
97     FunctionCall: "FunctionCall",
98     GCEvent: "GCEvent",
99
100     RequestAnimationFrame: "RequestAnimationFrame",
101     CancelAnimationFrame: "CancelAnimationFrame",
102     FireAnimationFrame: "FireAnimationFrame",
103
104     WebSocketCreate : "WebSocketCreate",
105     WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
106     WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
107     WebSocketDestroy : "WebSocketDestroy",
108 }
109
110 WebInspector.TimelineModel.Events = {
111     RecordAdded: "RecordAdded",
112     RecordsCleared: "RecordsCleared",
113     RecordingStarted: "RecordingStarted",
114     RecordingStopped: "RecordingStopped"
115 }
116
117 WebInspector.TimelineModel.startTimeInSeconds = function(record)
118 {
119     return record.startTime / 1000;
120 }
121
122 WebInspector.TimelineModel.endTimeInSeconds = function(record)
123 {
124     return (record.endTime || record.startTime) / 1000;
125 }
126
127 WebInspector.TimelineModel.durationInSeconds = function(record)
128 {
129     return WebInspector.TimelineModel.endTimeInSeconds(record) - WebInspector.TimelineModel.startTimeInSeconds(record);
130 }
131
132 /**
133  * @param {!Object} total
134  * @param {!Object} rawRecord
135  */
136 WebInspector.TimelineModel.aggregateTimeForRecord = function(total, rawRecord)
137 {
138     var childrenTime = 0;
139     var children = rawRecord["children"] || [];
140     for (var i = 0; i < children.length; ++i) {
141         WebInspector.TimelineModel.aggregateTimeForRecord(total, children[i]);
142         childrenTime += WebInspector.TimelineModel.durationInSeconds(children[i]);
143     }
144     var categoryName = WebInspector.TimelinePresentationModel.recordStyle(rawRecord).category.name;
145     var ownTime = WebInspector.TimelineModel.durationInSeconds(rawRecord) - childrenTime;
146     total[categoryName] = (total[categoryName] || 0) + ownTime;
147 }
148
149 /**
150  * @param {!Object} total
151  * @param {!Object} addend
152  */
153 WebInspector.TimelineModel.aggregateTimeByCategory = function(total, addend)
154 {
155     for (var category in addend)
156         total[category] = (total[category] || 0) + addend[category];
157 }
158
159 WebInspector.TimelineModel.prototype = {
160     /**
161      * @param {boolean=} includeCounters
162      */
163     startRecording: function(includeCounters)
164     {
165         this._clientInitiatedRecording = true;
166         this.reset();
167         var maxStackFrames = WebInspector.settings.timelineCaptureStacks.get() ? 30 : 0;
168         var includeGPUEvents = WebInspector.experimentsSettings.gpuTimeline.isEnabled();
169         WebInspector.timelineManager.start(maxStackFrames, includeCounters, includeGPUEvents, this._fireRecordingStarted.bind(this));
170     },
171
172     stopRecording: function()
173     {
174         if (!this._clientInitiatedRecording) {
175             WebInspector.timelineManager.start(undefined, undefined, undefined, stopTimeline.bind(this));
176             return;
177         }
178
179         /**
180          * Console started this one and we are just sniffing it. Initiate recording so that we
181          * could stop it.
182          * @this {WebInspector.TimelineModel}
183          */
184         function stopTimeline()
185         {
186             WebInspector.timelineManager.stop(this._fireRecordingStopped.bind(this));
187         }
188
189         this._clientInitiatedRecording = false;
190         WebInspector.timelineManager.stop(this._fireRecordingStopped.bind(this));
191     },
192
193     get records()
194     {
195         return this._records;
196     },
197
198     /**
199      * @param {!WebInspector.Event} event
200      */
201     _onRecordAdded: function(event)
202     {
203         if (this._collectionEnabled)
204             this._addRecord(/** @type {!TimelineAgent.TimelineEvent} */(event.data));
205     },
206
207     /**
208      * @param {!WebInspector.Event} event
209      */
210     _onStarted: function(event)
211     {
212         if (event.data) {
213             // Started from console.
214             this._fireRecordingStarted();
215         }
216     },
217
218     /**
219      * @param {!WebInspector.Event} event
220      */
221     _onStopped: function(event)
222     {
223         if (event.data) {
224             // Stopped from console.
225             this._fireRecordingStopped();
226         }
227     },
228
229     _fireRecordingStarted: function()
230     {
231         this._collectionEnabled = true;
232         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
233     },
234
235     _fireRecordingStopped: function()
236     {
237         this._collectionEnabled = false;
238         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
239     },
240
241     /**
242      * @param {!TimelineAgent.TimelineEvent} record
243      */
244     _addRecord: function(record)
245     {
246         this._stringPool.internObjectStrings(record);
247         this._records.push(record);
248         this._updateBoundaries(record);
249         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
250     },
251
252     /**
253      * @param {!Blob} file
254      * @param {!WebInspector.Progress} progress
255      */
256     loadFromFile: function(file, progress)
257     {
258         var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
259         var fileReader = this._createFileReader(file, delegate);
260         var loader = new WebInspector.TimelineModelLoader(this, fileReader, progress);
261         fileReader.start(loader);
262     },
263
264     /**
265      * @param {string} url
266      */
267     loadFromURL: function(url, progress)
268     {
269         var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
270         var urlReader = new WebInspector.ChunkedXHRReader(url, delegate);
271         var loader = new WebInspector.TimelineModelLoader(this, urlReader, progress);
272         urlReader.start(loader);
273     },
274
275     _createFileReader: function(file, delegate)
276     {
277         return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.TransferChunkLengthBytes, delegate);
278     },
279
280     _createFileWriter: function()
281     {
282         return new WebInspector.FileOutputStream();
283     },
284
285     saveToFile: function()
286     {
287         var now = new Date();
288         var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
289         var stream = this._createFileWriter();
290
291         /**
292          * @param {boolean} accepted
293          * @this {WebInspector.TimelineModel}
294          */
295         function callback(accepted)
296         {
297             if (!accepted)
298                 return;
299             var saver = new WebInspector.TimelineSaver(stream);
300             saver.save(this._records, window.navigator.appVersion);
301         }
302         stream.open(fileName, callback.bind(this));
303     },
304
305     reset: function()
306     {
307         this._records = [];
308         this._stringPool.reset();
309         this._minimumRecordTime = -1;
310         this._maximumRecordTime = -1;
311         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
312     },
313
314     /**
315      * @return {number}
316      */
317     minimumRecordTime: function()
318     {
319         return this._minimumRecordTime;
320     },
321
322     /**
323      * @return {number}
324      */
325     maximumRecordTime: function()
326     {
327         return this._maximumRecordTime;
328     },
329
330     /**
331      * @param {!TimelineAgent.TimelineEvent} record
332      */
333     _updateBoundaries: function(record)
334     {
335         var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
336         var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
337
338         if (this._minimumRecordTime === -1 || startTime < this._minimumRecordTime)
339             this._minimumRecordTime = startTime;
340         if (this._maximumRecordTime === -1 || endTime > this._maximumRecordTime)
341             this._maximumRecordTime = endTime;
342     },
343
344     /**
345      * @param {!Object} rawRecord
346      * @return {number}
347      */
348     recordOffsetInSeconds: function(rawRecord)
349     {
350         return WebInspector.TimelineModel.startTimeInSeconds(rawRecord) - this._minimumRecordTime;
351     },
352
353     __proto__: WebInspector.Object.prototype
354 }
355
356 /**
357  * @constructor
358  * @implements {WebInspector.OutputStream}
359  * @param {!WebInspector.TimelineModel} model
360  * @param {!{cancel: function()}} reader
361  * @param {!WebInspector.Progress} progress
362  */
363 WebInspector.TimelineModelLoader = function(model, reader, progress)
364 {
365     this._model = model;
366     this._reader = reader;
367     this._progress = progress;
368     this._buffer = "";
369     this._firstChunk = true;
370 }
371
372 WebInspector.TimelineModelLoader.prototype = {
373     /**
374      * @param {string} chunk
375      */
376     write: function(chunk)
377     {
378         var data = this._buffer + chunk;
379         var lastIndex = 0;
380         var index;
381         do {
382             index = lastIndex;
383             lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
384         } while (lastIndex !== -1)
385
386         var json = data.slice(0, index) + "]";
387         this._buffer = data.slice(index);
388
389         if (!index)
390             return;
391
392         // Prepending "0" to turn string into valid JSON.
393         if (!this._firstChunk)
394             json = "[0" + json;
395
396         var items;
397         try {
398             items = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (JSON.parse(json));
399         } catch (e) {
400             WebInspector.showErrorMessage("Malformed timeline data.");
401             this._model.reset();
402             this._reader.cancel();
403             this._progress.done();
404             return;
405         }
406
407         if (this._firstChunk) {
408             this._version = items[0];
409             this._firstChunk = false;
410             this._model.reset();
411         }
412
413         // Skip 0-th element - it is either version or 0.
414         for (var i = 1, size = items.length; i < size; ++i)
415             this._model._addRecord(items[i]);
416     },
417
418     close: function() { }
419 }
420
421 /**
422  * @constructor
423  * @implements {WebInspector.OutputStreamDelegate}
424  * @param {!WebInspector.TimelineModel} model
425  * @param {!WebInspector.Progress} progress
426  */
427 WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress)
428 {
429     this._model = model;
430     this._progress = progress;
431 }
432
433 WebInspector.TimelineModelLoadFromFileDelegate.prototype = {
434     onTransferStarted: function()
435     {
436         this._progress.setTitle(WebInspector.UIString("Loading\u2026"));
437     },
438
439     /**
440      * @param {!WebInspector.ChunkedReader} reader
441      */
442     onChunkTransferred: function(reader)
443     {
444         if (this._progress.isCanceled()) {
445             reader.cancel();
446             this._progress.done();
447             this._model.reset();
448             return;
449         }
450
451         var totalSize = reader.fileSize();
452         if (totalSize) {
453             this._progress.setTotalWork(totalSize);
454             this._progress.setWorked(reader.loadedSize());
455         }
456     },
457
458     onTransferFinished: function()
459     {
460         this._progress.done();
461     },
462
463     /**
464      * @param {!WebInspector.ChunkedReader} reader
465      */
466     onError: function(reader, event)
467     {
468         this._progress.done();
469         this._model.reset();
470         switch (event.target.error.code) {
471         case FileError.NOT_FOUND_ERR:
472             WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" not found.", reader.fileName()));
473             break;
474         case FileError.NOT_READABLE_ERR:
475             WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" is not readable", reader.fileName()));
476             break;
477         case FileError.ABORT_ERR:
478             break;
479         default:
480             WebInspector.showErrorMessage(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName()));
481         }
482     }
483 }
484
485 /**
486  * @constructor
487  */
488 WebInspector.TimelineSaver = function(stream)
489 {
490     this._stream = stream;
491 }
492
493 WebInspector.TimelineSaver.prototype = {
494     /**
495      * @param {!Array.<*>} records
496      * @param {string} version
497      */
498     save: function(records, version)
499     {
500         this._records = records;
501         this._recordIndex = 0;
502         this._prologue = "[" + JSON.stringify(version);
503
504         this._writeNextChunk(this._stream);
505     },
506
507     _writeNextChunk: function(stream)
508     {
509         const separator = ",\n";
510         var data = [];
511         var length = 0;
512
513         if (this._prologue) {
514             data.push(this._prologue);
515             length += this._prologue.length;
516             delete this._prologue;
517         } else {
518             if (this._recordIndex === this._records.length) {
519                 stream.close();
520                 return;
521             }
522             data.push("");
523         }
524         while (this._recordIndex < this._records.length) {
525             var item = JSON.stringify(this._records[this._recordIndex]);
526             var itemLength = item.length + separator.length;
527             if (length + itemLength > WebInspector.TimelineModel.TransferChunkLengthBytes)
528                 break;
529             length += itemLength;
530             data.push(item);
531             ++this._recordIndex;
532         }
533         if (this._recordIndex === this._records.length)
534             data.push(data.pop() + "]");
535         stream.write(data.join(separator), this._writeNextChunk.bind(this));
536     }
537 }
538
539 /**
540  * @constructor
541  */
542 WebInspector.TimelineMergingRecordBuffer = function()
543 {
544     this._backgroundRecordsBuffer = [];
545 }
546
547 /**
548  * @constructor
549  */
550 WebInspector.TimelineMergingRecordBuffer.prototype = {
551     /**
552      * @param {string} thread
553      * @param {!Array.<!TimelineAgent.TimelineEvent>} records
554      * @return {!Array.<!TimelineAgent.TimelineEvent>}
555      */
556     process: function(thread, records)
557     {
558         if (thread) {
559             this._backgroundRecordsBuffer = this._backgroundRecordsBuffer.concat(records);
560             return [];
561         }
562         var outputIndex = 0;
563         var result = new Array(this._backgroundRecordsBuffer.length + records.length);
564         var mainThreadRecord = 0;
565         var backgroundRecord = 0;
566         while  (backgroundRecord < this._backgroundRecordsBuffer.length && mainThreadRecord < records.length) {
567             if (this._backgroundRecordsBuffer[backgroundRecord].startTime < records[mainThreadRecord].startTime)
568                 result[outputIndex++] = this._backgroundRecordsBuffer[backgroundRecord++];
569             else
570                 result[outputIndex++] = records[mainThreadRecord++];
571         }
572         for (;mainThreadRecord < records.length; ++mainThreadRecord)
573             result[outputIndex++] = records[mainThreadRecord];
574         for (;backgroundRecord < this._backgroundRecordsBuffer.length; ++backgroundRecord)
575             result[outputIndex++] = this._backgroundRecordsBuffer[backgroundRecord];
576         this._backgroundRecordsBuffer = [];
577         return result;
578     }
579 };