1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 * @extends {WebInspector.TimelineModel}
8 * @implements {WebInspector.TargetManager.Observer}
10 WebInspector.TimelineModelImpl = function()
12 WebInspector.TimelineModel.call(this);
13 /** @type {?WebInspector.Target} */
14 this._currentTarget = null;
16 this._bindings = new WebInspector.TimelineModelImpl.InterRecordBindings();
20 WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this);
21 WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, WebInspector.TimelineManager.EventTypes.TimelineStarted, this._onStarted, this);
22 WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, WebInspector.TimelineManager.EventTypes.TimelineStopped, this._onStopped, this);
23 WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, WebInspector.TimelineManager.EventTypes.TimelineProgress, this._onProgress, this);
24 WebInspector.targetManager.observeTargets(this);
27 WebInspector.TimelineModelImpl.TransferChunkLengthBytes = 5000000;
29 WebInspector.TimelineModelImpl.prototype = {
31 * @param {!WebInspector.Target} target
33 targetAdded: function(target) { },
36 * @param {!WebInspector.Target} target
38 targetRemoved: function(target)
40 if (this._currentTarget === target)
41 this._currentTarget = null;
45 * @param {boolean} captureStacks
46 * @param {boolean} captureMemory
47 * @param {boolean} capturePictures
49 startRecording: function(captureStacks, captureMemory, capturePictures)
51 console.assert(!capturePictures, "Legacy timeline does not support capturing pictures");
53 this._currentTarget = WebInspector.context.flavor(WebInspector.Target);
54 console.assert(this._currentTarget);
56 this._clientInitiatedRecording = true;
57 var maxStackFrames = captureStacks ? 30 : 0;
58 var includeGPUEvents = WebInspector.experimentsSettings.gpuTimeline.isEnabled();
59 var liveEvents = [ WebInspector.TimelineModel.RecordType.BeginFrame,
60 WebInspector.TimelineModel.RecordType.DrawFrame,
61 WebInspector.TimelineModel.RecordType.RequestMainThreadFrame,
62 WebInspector.TimelineModel.RecordType.ActivateLayerTree ];
63 this._currentTarget.timelineManager.start(maxStackFrames, liveEvents.join(","), captureMemory, includeGPUEvents, this._fireRecordingStarted.bind(this));
66 stopRecording: function()
68 if (!this._currentTarget)
71 if (!this._clientInitiatedRecording) {
72 this._currentTarget.timelineManager.start(undefined, undefined, undefined, undefined, stopTimeline.bind(this));
77 * Console started this one and we are just sniffing it. Initiate recording so that we
79 * @this {WebInspector.TimelineModelImpl}
81 function stopTimeline()
83 this._currentTarget.timelineManager.stop(this._fireRecordingStopped.bind(this));
86 this._clientInitiatedRecording = false;
87 this._currentTarget.timelineManager.stop(this._fireRecordingStopped.bind(this));
91 * @return {!Array.<!WebInspector.TimelineModel.Record>}
99 * @param {!WebInspector.Event} event
101 _onRecordAdded: function(event)
103 var timelineManager = /** @type {!WebInspector.TimelineManager} */ (event.target);
104 if (this._collectionEnabled && timelineManager.target() === this._currentTarget)
105 this._addRecord(/** @type {!TimelineAgent.TimelineEvent} */(event.data));
109 * @param {!WebInspector.Event} event
111 _onStarted: function(event)
113 if (!event.data || this._collectionEnabled)
115 // Started from console.
116 var timelineManager = /** @type {!WebInspector.TimelineManager} */ (event.target);
117 if (this._currentTarget !== timelineManager.target()) {
119 this._currentTarget = timelineManager.target();
121 this._fireRecordingStarted();
125 * @param {!WebInspector.Event} event
127 _onStopped: function(event)
129 var timelineManager = /** @type {!WebInspector.TimelineManager} */ (event.target);
130 if (timelineManager.target() !== this._currentTarget)
132 // We were buffering events, discard those that got through, the real ones are coming!
134 this._currentTarget = timelineManager.target();
136 var events = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (event.data.events);
137 for (var i = 0; i < events.length; ++i)
138 this._addRecord(events[i]);
140 if (event.data.consoleTimeline) {
141 // Stopped from console.
142 this._fireRecordingStopped(null, null);
145 this._collectionEnabled = false;
149 * @param {!WebInspector.Event} event
151 _onProgress: function(event)
153 var timelineManager = /** @type {!WebInspector.TimelineManager} */ (event.target);
154 if (timelineManager.target() === this._currentTarget)
155 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingProgress, event.data);
158 _fireRecordingStarted: function()
160 this._collectionEnabled = true;
161 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
165 * @param {?Protocol.Error} error
166 * @param {?ProfilerAgent.CPUProfile} cpuProfile
168 _fireRecordingStopped: function(error, cpuProfile)
171 WebInspector.TimelineJSProfileProcessor.mergeJSProfileIntoTimeline(this, cpuProfile);
172 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
176 * @param {!TimelineAgent.TimelineEvent} payload
178 _addRecord: function(payload)
180 this._internStrings(payload);
181 this._payloads.push(payload);
183 var record = this._innerAddRecord(payload, null);
184 this._updateBoundaries(record);
185 this._records.push(record);
186 if (record.type() === WebInspector.TimelineModel.RecordType.Program)
187 this._mainThreadTasks.push(record);
188 if (record.type() === WebInspector.TimelineModel.RecordType.GPUTask)
189 this._gpuThreadTasks.push(record);
191 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
195 * @param {!TimelineAgent.TimelineEvent} payload
196 * @param {?WebInspector.TimelineModel.Record} parentRecord
197 * @return {!WebInspector.TimelineModel.Record}
199 _innerAddRecord: function(payload, parentRecord)
201 var record = new WebInspector.TimelineModel.RecordImpl(this, payload, parentRecord);
202 if (WebInspector.TimelineUIUtilsImpl.isEventDivider(record))
203 this._eventDividerRecords.push(record);
205 for (var i = 0; payload.children && i < payload.children.length; ++i)
206 this._innerAddRecord.call(this, payload.children[i], record);
209 parentRecord._selfTime -= record.endTime() - record.startTime();
214 * @param {!WebInspector.ChunkedFileReader} fileReader
215 * @param {!WebInspector.Progress} progress
216 * @return {!WebInspector.OutputStream}
218 createLoader: function(fileReader, progress)
220 return new WebInspector.TimelineModelLoader(this, fileReader, progress);
224 * @param {!WebInspector.OutputStream} stream
226 writeToStream: function(stream)
228 var saver = new WebInspector.TimelineSaver(stream);
229 saver.save(this._payloads, window.navigator.appVersion);
234 if (!this._collectionEnabled)
235 this._currentTarget = null;
237 this._stringPool = {};
238 this._bindings._reset();
239 WebInspector.TimelineModel.prototype.reset.call(this);
243 * @param {!TimelineAgent.TimelineEvent} record
245 _internStrings: function(record)
247 for (var name in record) {
248 var value = record[name];
249 if (typeof value !== "string")
252 var interned = this._stringPool[value];
253 if (typeof interned === "string")
254 record[name] = interned;
256 this._stringPool[value] = value;
259 var children = record.children;
260 for (var i = 0; children && i < children.length; ++i)
261 this._internStrings(children[i]);
264 __proto__: WebInspector.TimelineModel.prototype
271 WebInspector.TimelineModelImpl.InterRecordBindings = function() {
275 WebInspector.TimelineModelImpl.InterRecordBindings.prototype = {
278 this._sendRequestRecords = {};
279 this._timerRecords = {};
280 this._requestAnimationFrameRecords = {};
281 this._layoutInvalidate = {};
282 this._lastScheduleStyleRecalculation = {};
283 this._webSocketCreateRecords = {};
289 * @implements {WebInspector.TimelineModel.Record}
290 * @param {!WebInspector.TimelineModel} model
291 * @param {!TimelineAgent.TimelineEvent} timelineEvent
292 * @param {?WebInspector.TimelineModel.Record} parentRecord
294 WebInspector.TimelineModel.RecordImpl = function(model, timelineEvent, parentRecord)
297 var bindings = this._model._bindings;
298 this._record = timelineEvent;
299 this._thread = this._record.thread || WebInspector.TimelineModel.MainThreadName;
302 this.parent = parentRecord;
303 parentRecord.children().push(this);
306 this._selfTime = this.endTime() - this.startTime();
308 var recordTypes = WebInspector.TimelineModel.RecordType;
309 switch (timelineEvent.type) {
310 case recordTypes.ResourceSendRequest:
311 // Make resource receive record last since request was sent; make finish record last since response received.
312 bindings._sendRequestRecords[timelineEvent.data["requestId"]] = this;
315 case recordTypes.ResourceReceiveResponse:
316 case recordTypes.ResourceReceivedData:
317 case recordTypes.ResourceFinish:
318 this._initiator = bindings._sendRequestRecords[timelineEvent.data["requestId"]];
321 case recordTypes.TimerInstall:
322 bindings._timerRecords[timelineEvent.data["timerId"]] = this;
325 case recordTypes.TimerFire:
326 this._initiator = bindings._timerRecords[timelineEvent.data["timerId"]];
329 case recordTypes.RequestAnimationFrame:
330 bindings._requestAnimationFrameRecords[timelineEvent.data["id"]] = this;
333 case recordTypes.FireAnimationFrame:
334 this._initiator = bindings._requestAnimationFrameRecords[timelineEvent.data["id"]];
337 case recordTypes.ScheduleStyleRecalculation:
338 bindings._lastScheduleStyleRecalculation[this.frameId()] = this;
341 case recordTypes.RecalculateStyles:
342 this._initiator = bindings._lastScheduleStyleRecalculation[this.frameId()];
345 case recordTypes.InvalidateLayout:
346 // Consider style recalculation as a reason for layout invalidation,
347 // but only if we had no earlier layout invalidation records.
348 var layoutInitator = this;
349 if (!bindings._layoutInvalidate[this.frameId()] && parentRecord.type() === recordTypes.RecalculateStyles)
350 layoutInitator = parentRecord._initiator;
351 bindings._layoutInvalidate[this.frameId()] = layoutInitator;
354 case recordTypes.Layout:
355 this._initiator = bindings._layoutInvalidate[this.frameId()];
356 bindings._layoutInvalidate[this.frameId()] = null;
357 if (this.stackTrace())
358 this.addWarning(WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
361 case recordTypes.WebSocketCreate:
362 bindings._webSocketCreateRecords[timelineEvent.data["identifier"]] = this;
365 case recordTypes.WebSocketSendHandshakeRequest:
366 case recordTypes.WebSocketReceiveHandshakeResponse:
367 case recordTypes.WebSocketDestroy:
368 this._initiator = bindings._webSocketCreateRecords[timelineEvent.data["identifier"]];
373 WebInspector.TimelineModel.RecordImpl.prototype = {
375 * @return {?Array.<!ConsoleAgent.CallFrame>}
377 callSiteStackTrace: function()
379 return this._initiator ? this._initiator.stackTrace() : null;
383 * @return {?WebInspector.TimelineModel.Record}
385 initiator: function()
387 return this._initiator;
391 * @return {?WebInspector.Target}
395 return this._model._currentTarget;
403 return this._selfTime;
407 * @return {!Array.<!WebInspector.TimelineModel.Record>}
411 return this._children;
417 startTime: function()
419 return this._record.startTime;
435 return this._endTime || this._record.endTime || this._record.startTime;
439 * @param {number} endTime
441 setEndTime: function(endTime)
443 this._endTime = endTime;
451 return this._record.data;
459 return this._record.type;
467 return this._record.frameId || "";
471 * @return {?Array.<!ConsoleAgent.CallFrame>}
473 stackTrace: function()
475 if (this._record.stackTrace && this._record.stackTrace.length)
476 return this._record.stackTrace;
481 * @param {string} key
484 getUserObject: function(key)
486 if (!this._userObjects)
488 return this._userObjects.get(key);
492 * @param {string} key
493 * @param {?Object|undefined} value
495 setUserObject: function(key, value)
497 if (!this._userObjects)
498 this._userObjects = new StringMap();
499 this._userObjects.put(key, value);
503 * @param {string} message
505 addWarning: function(message)
509 this._warnings.push(message);
513 * @return {?Array.<string>}
517 return this._warnings;
523 * @implements {WebInspector.OutputStream}
524 * @param {!WebInspector.TimelineModel} model
525 * @param {!{cancel: function()}} reader
526 * @param {!WebInspector.Progress} progress
528 WebInspector.TimelineModelLoader = function(model, reader, progress)
531 this._reader = reader;
532 this._progress = progress;
534 this._firstChunk = true;
537 WebInspector.TimelineModelLoader.prototype = {
539 * @param {string} chunk
541 write: function(chunk)
543 var data = this._buffer + chunk;
548 lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
549 } while (lastIndex !== -1)
551 var json = data.slice(0, index) + "]";
552 this._buffer = data.slice(index);
557 if (this._firstChunk) {
558 this._firstChunk = false;
561 // Prepending "0" to turn string into valid JSON.
567 items = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (JSON.parse(json));
569 WebInspector.console.error("Malformed timeline data.");
571 this._reader.cancel();
572 this._progress.done();
576 // Skip 0-th element - it is either version or 0.
577 for (var i = 1, size = items.length; i < size; ++i)
578 this._model._addRecord(items[i]);
588 * @param {!WebInspector.OutputStream} stream
590 WebInspector.TimelineSaver = function(stream)
592 this._stream = stream;
595 WebInspector.TimelineSaver.prototype = {
597 * @param {!Array.<*>} payloads
598 * @param {string} version
600 save: function(payloads, version)
602 this._payloads = payloads;
603 this._recordIndex = 0;
604 this._prologue = "[" + JSON.stringify(version);
606 this._writeNextChunk(this._stream);
609 _writeNextChunk: function(stream)
611 const separator = ",\n";
615 if (this._prologue) {
616 data.push(this._prologue);
617 length += this._prologue.length;
618 delete this._prologue;
620 if (this._recordIndex === this._payloads.length) {
626 while (this._recordIndex < this._payloads.length) {
627 var item = JSON.stringify(this._payloads[this._recordIndex]);
628 var itemLength = item.length + separator.length;
629 if (length + itemLength > WebInspector.TimelineModelImpl.TransferChunkLengthBytes)
631 length += itemLength;
635 if (this._recordIndex === this._payloads.length)
636 data.push(data.pop() + "]");
637 stream.write(data.join(separator), this._writeNextChunk.bind(this));