2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 * @extends {WebInspector.Object}
35 WebInspector.TimelineModel = function()
38 this._stringPool = new StringPool();
39 this._minimumRecordTime = -1;
40 this._maximumRecordTime = -1;
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);
47 WebInspector.TimelineModel.TransferChunkLengthBytes = 5000000;
49 WebInspector.TimelineModel.RecordType = {
52 EventDispatch: "EventDispatch",
56 RequestMainThreadFrame: "RequestMainThreadFrame",
57 BeginFrame: "BeginFrame",
58 ActivateLayerTree: "ActivateLayerTree",
59 DrawFrame: "DrawFrame",
60 ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
61 RecalculateStyles: "RecalculateStyles",
62 InvalidateLayout: "InvalidateLayout",
64 AutosizeText: "AutosizeText",
65 PaintSetup: "PaintSetup",
67 Rasterize: "Rasterize",
68 ScrollLayer: "ScrollLayer",
69 DecodeImage: "DecodeImage",
70 ResizeImage: "ResizeImage",
71 CompositeLayers: "CompositeLayers",
73 ParseHTML: "ParseHTML",
75 TimerInstall: "TimerInstall",
76 TimerRemove: "TimerRemove",
77 TimerFire: "TimerFire",
79 XHRReadyStateChange: "XHRReadyStateChange",
81 EvaluateScript: "EvaluateScript",
84 MarkDOMContent: "MarkDOMContent",
85 MarkFirstPaint: "MarkFirstPaint",
87 TimeStamp: "TimeStamp",
91 ScheduleResourceRequest: "ScheduleResourceRequest",
92 ResourceSendRequest: "ResourceSendRequest",
93 ResourceReceiveResponse: "ResourceReceiveResponse",
94 ResourceReceivedData: "ResourceReceivedData",
95 ResourceFinish: "ResourceFinish",
97 FunctionCall: "FunctionCall",
100 RequestAnimationFrame: "RequestAnimationFrame",
101 CancelAnimationFrame: "CancelAnimationFrame",
102 FireAnimationFrame: "FireAnimationFrame",
104 WebSocketCreate : "WebSocketCreate",
105 WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
106 WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
107 WebSocketDestroy : "WebSocketDestroy",
110 WebInspector.TimelineModel.Events = {
111 RecordAdded: "RecordAdded",
112 RecordsCleared: "RecordsCleared",
113 RecordingStarted: "RecordingStarted",
114 RecordingStopped: "RecordingStopped"
117 WebInspector.TimelineModel.startTimeInSeconds = function(record)
119 return record.startTime / 1000;
122 WebInspector.TimelineModel.endTimeInSeconds = function(record)
124 return (record.endTime || record.startTime) / 1000;
127 WebInspector.TimelineModel.durationInSeconds = function(record)
129 return WebInspector.TimelineModel.endTimeInSeconds(record) - WebInspector.TimelineModel.startTimeInSeconds(record);
133 * @param {!Object} total
134 * @param {!Object} rawRecord
136 WebInspector.TimelineModel.aggregateTimeForRecord = function(total, rawRecord)
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]);
144 var categoryName = WebInspector.TimelinePresentationModel.recordStyle(rawRecord).category.name;
145 var ownTime = WebInspector.TimelineModel.durationInSeconds(rawRecord) - childrenTime;
146 total[categoryName] = (total[categoryName] || 0) + ownTime;
150 * @param {!Object} total
151 * @param {!Object} addend
153 WebInspector.TimelineModel.aggregateTimeByCategory = function(total, addend)
155 for (var category in addend)
156 total[category] = (total[category] || 0) + addend[category];
159 WebInspector.TimelineModel.prototype = {
161 * @param {boolean=} includeCounters
163 startRecording: function(includeCounters)
165 this._clientInitiatedRecording = true;
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));
172 stopRecording: function()
174 if (!this._clientInitiatedRecording) {
175 WebInspector.timelineManager.start(undefined, undefined, undefined, stopTimeline.bind(this));
180 * Console started this one and we are just sniffing it. Initiate recording so that we
182 * @this {WebInspector.TimelineModel}
184 function stopTimeline()
186 WebInspector.timelineManager.stop(this._fireRecordingStopped.bind(this));
189 this._clientInitiatedRecording = false;
190 WebInspector.timelineManager.stop(this._fireRecordingStopped.bind(this));
195 return this._records;
199 * @param {!WebInspector.Event} event
201 _onRecordAdded: function(event)
203 if (this._collectionEnabled)
204 this._addRecord(/** @type {!TimelineAgent.TimelineEvent} */(event.data));
208 * @param {!WebInspector.Event} event
210 _onStarted: function(event)
213 // Started from console.
214 this._fireRecordingStarted();
219 * @param {!WebInspector.Event} event
221 _onStopped: function(event)
224 // Stopped from console.
225 this._fireRecordingStopped();
229 _fireRecordingStarted: function()
231 this._collectionEnabled = true;
232 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
235 _fireRecordingStopped: function()
237 this._collectionEnabled = false;
238 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
242 * @param {!TimelineAgent.TimelineEvent} record
244 _addRecord: function(record)
246 this._stringPool.internObjectStrings(record);
247 this._records.push(record);
248 this._updateBoundaries(record);
249 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
253 * @param {!Blob} file
254 * @param {!WebInspector.Progress} progress
256 loadFromFile: function(file, progress)
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);
265 * @param {string} url
267 loadFromURL: function(url, progress)
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);
275 _createFileReader: function(file, delegate)
277 return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.TransferChunkLengthBytes, delegate);
280 _createFileWriter: function()
282 return new WebInspector.FileOutputStream();
285 saveToFile: function()
287 var now = new Date();
288 var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
289 var stream = this._createFileWriter();
292 * @param {boolean} accepted
293 * @this {WebInspector.TimelineModel}
295 function callback(accepted)
299 var saver = new WebInspector.TimelineSaver(stream);
300 saver.save(this._records, window.navigator.appVersion);
302 stream.open(fileName, callback.bind(this));
308 this._stringPool.reset();
309 this._minimumRecordTime = -1;
310 this._maximumRecordTime = -1;
311 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
317 minimumRecordTime: function()
319 return this._minimumRecordTime;
325 maximumRecordTime: function()
327 return this._maximumRecordTime;
331 * @param {!TimelineAgent.TimelineEvent} record
333 _updateBoundaries: function(record)
335 var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
336 var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
338 if (this._minimumRecordTime === -1 || startTime < this._minimumRecordTime)
339 this._minimumRecordTime = startTime;
340 if (this._maximumRecordTime === -1 || endTime > this._maximumRecordTime)
341 this._maximumRecordTime = endTime;
345 * @param {!Object} rawRecord
348 recordOffsetInSeconds: function(rawRecord)
350 return WebInspector.TimelineModel.startTimeInSeconds(rawRecord) - this._minimumRecordTime;
353 __proto__: WebInspector.Object.prototype
358 * @implements {WebInspector.OutputStream}
359 * @param {!WebInspector.TimelineModel} model
360 * @param {!{cancel: function()}} reader
361 * @param {!WebInspector.Progress} progress
363 WebInspector.TimelineModelLoader = function(model, reader, progress)
366 this._reader = reader;
367 this._progress = progress;
369 this._firstChunk = true;
372 WebInspector.TimelineModelLoader.prototype = {
374 * @param {string} chunk
376 write: function(chunk)
378 var data = this._buffer + chunk;
383 lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
384 } while (lastIndex !== -1)
386 var json = data.slice(0, index) + "]";
387 this._buffer = data.slice(index);
392 // Prepending "0" to turn string into valid JSON.
393 if (!this._firstChunk)
398 items = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (JSON.parse(json));
400 WebInspector.showErrorMessage("Malformed timeline data.");
402 this._reader.cancel();
403 this._progress.done();
407 if (this._firstChunk) {
408 this._version = items[0];
409 this._firstChunk = false;
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]);
418 close: function() { }
423 * @implements {WebInspector.OutputStreamDelegate}
424 * @param {!WebInspector.TimelineModel} model
425 * @param {!WebInspector.Progress} progress
427 WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress)
430 this._progress = progress;
433 WebInspector.TimelineModelLoadFromFileDelegate.prototype = {
434 onTransferStarted: function()
436 this._progress.setTitle(WebInspector.UIString("Loading\u2026"));
440 * @param {!WebInspector.ChunkedReader} reader
442 onChunkTransferred: function(reader)
444 if (this._progress.isCanceled()) {
446 this._progress.done();
451 var totalSize = reader.fileSize();
453 this._progress.setTotalWork(totalSize);
454 this._progress.setWorked(reader.loadedSize());
458 onTransferFinished: function()
460 this._progress.done();
464 * @param {!WebInspector.ChunkedReader} reader
466 onError: function(reader, event)
468 this._progress.done();
470 switch (event.target.error.code) {
471 case FileError.NOT_FOUND_ERR:
472 WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" not found.", reader.fileName()));
474 case FileError.NOT_READABLE_ERR:
475 WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" is not readable", reader.fileName()));
477 case FileError.ABORT_ERR:
480 WebInspector.showErrorMessage(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName()));
488 WebInspector.TimelineSaver = function(stream)
490 this._stream = stream;
493 WebInspector.TimelineSaver.prototype = {
495 * @param {!Array.<*>} records
496 * @param {string} version
498 save: function(records, version)
500 this._records = records;
501 this._recordIndex = 0;
502 this._prologue = "[" + JSON.stringify(version);
504 this._writeNextChunk(this._stream);
507 _writeNextChunk: function(stream)
509 const separator = ",\n";
513 if (this._prologue) {
514 data.push(this._prologue);
515 length += this._prologue.length;
516 delete this._prologue;
518 if (this._recordIndex === this._records.length) {
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)
529 length += itemLength;
533 if (this._recordIndex === this._records.length)
534 data.push(data.pop() + "]");
535 stream.write(data.join(separator), this._writeNextChunk.bind(this));
542 WebInspector.TimelineMergingRecordBuffer = function()
544 this._backgroundRecordsBuffer = [];
550 WebInspector.TimelineMergingRecordBuffer.prototype = {
552 * @param {string} thread
553 * @param {!Array.<!TimelineAgent.TimelineEvent>} records
554 * @return {!Array.<!TimelineAgent.TimelineEvent>}
556 process: function(thread, records)
559 this._backgroundRecordsBuffer = this._backgroundRecordsBuffer.concat(records);
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++];
570 result[outputIndex++] = records[mainThreadRecord++];
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 = [];