OLD | NEW |
| (Empty) |
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. | |
4 | |
5 /** | |
6 * @constructor | |
7 * @extends {WebInspector.TimelineModel} | |
8 * @implements {WebInspector.TargetManager.Observer} | |
9 */ | |
10 WebInspector.TimelineModelImpl = function() | |
11 { | |
12 WebInspector.TimelineModel.call(this); | |
13 /** @type {?WebInspector.Target} */ | |
14 this._currentTarget = null; | |
15 this._filters = []; | |
16 this._bindings = new WebInspector.TimelineModelImpl.InterRecordBindings(); | |
17 | |
18 this.reset(); | |
19 | |
20 WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, We
bInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded
, this); | |
21 WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, We
bInspector.TimelineManager.EventTypes.TimelineStarted, this._onStarted, this); | |
22 WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, We
bInspector.TimelineManager.EventTypes.TimelineStopped, this._onStopped, this); | |
23 WebInspector.targetManager.addModelListener(WebInspector.TimelineManager, We
bInspector.TimelineManager.EventTypes.TimelineProgress, this._onProgress, this); | |
24 WebInspector.targetManager.observeTargets(this); | |
25 } | |
26 | |
27 WebInspector.TimelineModelImpl.TransferChunkLengthBytes = 5000000; | |
28 | |
29 WebInspector.TimelineModelImpl.prototype = { | |
30 /** | |
31 * @param {!WebInspector.Target} target | |
32 */ | |
33 targetAdded: function(target) { }, | |
34 | |
35 /** | |
36 * @param {!WebInspector.Target} target | |
37 */ | |
38 targetRemoved: function(target) | |
39 { | |
40 if (this._currentTarget === target) | |
41 this._currentTarget = null; | |
42 }, | |
43 | |
44 /** | |
45 * @param {boolean} captureCauses | |
46 * @param {boolean} captureMemory | |
47 * @param {boolean} capturePictures | |
48 */ | |
49 startRecording: function(captureCauses, captureMemory, capturePictures) | |
50 { | |
51 console.assert(!capturePictures, "Legacy timeline does not support captu
ring pictures"); | |
52 this.reset(); | |
53 this._currentTarget = WebInspector.context.flavor(WebInspector.Target); | |
54 console.assert(this._currentTarget); | |
55 | |
56 this._clientInitiatedRecording = true; | |
57 var maxStackFrames = captureCauses ? 30 : 0; | |
58 var includeGPUEvents = Runtime.experiments.isEnabled("gpuTimeline"); | |
59 var liveEvents = [ WebInspector.TimelineModel.RecordType.BeginFrame, | |
60 WebInspector.TimelineModel.RecordType.DrawFrame, | |
61 WebInspector.TimelineModel.RecordType.RequestMainThre
adFrame, | |
62 WebInspector.TimelineModel.RecordType.ActivateLayerTr
ee ]; | |
63 this._currentTarget.timelineManager.start(maxStackFrames, liveEvents.joi
n(","), captureMemory, includeGPUEvents, this._fireRecordingStarted.bind(this)); | |
64 }, | |
65 | |
66 stopRecording: function() | |
67 { | |
68 if (!this._currentTarget) | |
69 return; | |
70 | |
71 if (!this._clientInitiatedRecording) { | |
72 this._currentTarget.timelineManager.start(undefined, undefined, unde
fined, undefined, stopTimeline.bind(this)); | |
73 return; | |
74 } | |
75 | |
76 /** | |
77 * Console started this one and we are just sniffing it. Initiate record
ing so that we | |
78 * could stop it. | |
79 * @this {WebInspector.TimelineModelImpl} | |
80 */ | |
81 function stopTimeline() | |
82 { | |
83 this._currentTarget.timelineManager.stop(this._fireRecordingStopped.
bind(this)); | |
84 } | |
85 | |
86 this._clientInitiatedRecording = false; | |
87 this._currentTarget.timelineManager.stop(this._fireRecordingStopped.bind
(this)); | |
88 }, | |
89 | |
90 /** | |
91 * @return {!Array.<!WebInspector.TimelineModel.Record>} | |
92 */ | |
93 records: function() | |
94 { | |
95 return this._records; | |
96 }, | |
97 | |
98 /** | |
99 * @param {!WebInspector.Event} event | |
100 */ | |
101 _onRecordAdded: function(event) | |
102 { | |
103 var timelineManager = /** @type {!WebInspector.TimelineManager} */ (even
t.target); | |
104 if (this._collectionEnabled && timelineManager.target() === this._curren
tTarget) | |
105 this._addRecord(/** @type {!TimelineAgent.TimelineEvent} */(event.da
ta)); | |
106 }, | |
107 | |
108 /** | |
109 * @param {!WebInspector.Event} event | |
110 */ | |
111 _onStarted: function(event) | |
112 { | |
113 if (!event.data || this._collectionEnabled) | |
114 return; | |
115 // Started from console. | |
116 var timelineManager = /** @type {!WebInspector.TimelineManager} */ (even
t.target); | |
117 if (this._currentTarget !== timelineManager.target()) { | |
118 this.reset(); | |
119 this._currentTarget = timelineManager.target(); | |
120 } | |
121 this._fireRecordingStarted(); | |
122 }, | |
123 | |
124 /** | |
125 * @param {!WebInspector.Event} event | |
126 */ | |
127 _onStopped: function(event) | |
128 { | |
129 var timelineManager = /** @type {!WebInspector.TimelineManager} */ (even
t.target); | |
130 if (timelineManager.target() !== this._currentTarget) | |
131 return; | |
132 // We were buffering events, discard those that got through, the real on
es are coming! | |
133 this.reset(); | |
134 this._currentTarget = timelineManager.target(); | |
135 | |
136 var events = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (event
.data.events); | |
137 for (var i = 0; i < events.length; ++i) | |
138 this._addRecord(events[i]); | |
139 | |
140 if (event.data.consoleTimeline) { | |
141 // Stopped from console. | |
142 this._fireRecordingStopped(null); | |
143 } | |
144 | |
145 this._collectionEnabled = false; | |
146 }, | |
147 | |
148 /** | |
149 * @param {!WebInspector.Event} event | |
150 */ | |
151 _onProgress: function(event) | |
152 { | |
153 var timelineManager = /** @type {!WebInspector.TimelineManager} */ (even
t.target); | |
154 if (timelineManager.target() === this._currentTarget) | |
155 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.Reco
rdingProgress, event.data); | |
156 }, | |
157 | |
158 _fireRecordingStarted: function() | |
159 { | |
160 this._collectionEnabled = true; | |
161 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.Recordin
gStarted); | |
162 }, | |
163 | |
164 /** | |
165 * @param {?Protocol.Error} error | |
166 */ | |
167 _fireRecordingStopped: function(error) | |
168 { | |
169 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.Recordin
gStopped); | |
170 }, | |
171 | |
172 /** | |
173 * @param {!TimelineAgent.TimelineEvent} payload | |
174 */ | |
175 _addRecord: function(payload) | |
176 { | |
177 this._internStrings(payload); | |
178 this._payloads.push(payload); | |
179 | |
180 var record = this._innerAddRecord(payload, null); | |
181 this._updateBoundaries(record); | |
182 this._records.push(record); | |
183 if (record.type() === WebInspector.TimelineModel.RecordType.Program) | |
184 this._mainThreadTasks.push(record); | |
185 if (record.type() === WebInspector.TimelineModel.RecordType.GPUTask) | |
186 this._gpuThreadTasks.push(record); | |
187 | |
188 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAd
ded, record); | |
189 }, | |
190 | |
191 /** | |
192 * @param {!TimelineAgent.TimelineEvent} payload | |
193 * @param {?WebInspector.TimelineModel.Record} parentRecord | |
194 * @return {!WebInspector.TimelineModel.Record} | |
195 */ | |
196 _innerAddRecord: function(payload, parentRecord) | |
197 { | |
198 var record = new WebInspector.TimelineModel.RecordImpl(this, payload, pa
rentRecord); | |
199 if (WebInspector.TimelineUIUtilsImpl.isEventDivider(record)) | |
200 this._eventDividerRecords.push(record); | |
201 | |
202 for (var i = 0; payload.children && i < payload.children.length; ++i) | |
203 this._innerAddRecord.call(this, payload.children[i], record); | |
204 | |
205 if (parentRecord) | |
206 parentRecord._selfTime -= record.endTime() - record.startTime(); | |
207 return record; | |
208 }, | |
209 | |
210 /** | |
211 * @param {!WebInspector.ChunkedFileReader} fileReader | |
212 * @param {!WebInspector.Progress} progress | |
213 * @return {!WebInspector.OutputStream} | |
214 */ | |
215 createLoader: function(fileReader, progress) | |
216 { | |
217 return new WebInspector.TimelineModelLoader(this, fileReader, progress); | |
218 }, | |
219 | |
220 /** | |
221 * @param {!WebInspector.OutputStream} stream | |
222 */ | |
223 writeToStream: function(stream) | |
224 { | |
225 var saver = new WebInspector.TimelineSaver(stream); | |
226 saver.save(this._payloads, window.navigator.appVersion); | |
227 }, | |
228 | |
229 reset: function() | |
230 { | |
231 if (!this._collectionEnabled) | |
232 this._currentTarget = null; | |
233 this._payloads = []; | |
234 this._stringPool = {}; | |
235 this._bindings._reset(); | |
236 this._minimumRecordTime = 0; | |
237 this._maximumRecordTime = 0; | |
238 WebInspector.TimelineModel.prototype.reset.call(this); | |
239 }, | |
240 | |
241 /** | |
242 * @return {number} | |
243 */ | |
244 minimumRecordTime: function() | |
245 { | |
246 return this._minimumRecordTime; | |
247 }, | |
248 | |
249 /** | |
250 * @return {number} | |
251 */ | |
252 maximumRecordTime: function() | |
253 { | |
254 return this._maximumRecordTime; | |
255 }, | |
256 | |
257 /** | |
258 * @param {!WebInspector.TimelineModel.Record} record | |
259 */ | |
260 _updateBoundaries: function(record) | |
261 { | |
262 var startTime = record.startTime(); | |
263 var endTime = record.endTime(); | |
264 | |
265 if (!this._minimumRecordTime || startTime < this._minimumRecordTime) | |
266 this._minimumRecordTime = startTime; | |
267 if (endTime > this._maximumRecordTime) | |
268 this._maximumRecordTime = endTime; | |
269 }, | |
270 | |
271 /** | |
272 * @param {!TimelineAgent.TimelineEvent} record | |
273 */ | |
274 _internStrings: function(record) | |
275 { | |
276 for (var name in record) { | |
277 var value = record[name]; | |
278 if (typeof value !== "string") | |
279 continue; | |
280 | |
281 var interned = this._stringPool[value]; | |
282 if (typeof interned === "string") | |
283 record[name] = interned; | |
284 else | |
285 this._stringPool[value] = value; | |
286 } | |
287 | |
288 var children = record.children; | |
289 for (var i = 0; children && i < children.length; ++i) | |
290 this._internStrings(children[i]); | |
291 }, | |
292 | |
293 __proto__: WebInspector.TimelineModel.prototype | |
294 } | |
295 | |
296 | |
297 /** | |
298 * @constructor | |
299 */ | |
300 WebInspector.TimelineModelImpl.InterRecordBindings = function() { | |
301 this._reset(); | |
302 } | |
303 | |
304 WebInspector.TimelineModelImpl.InterRecordBindings.prototype = { | |
305 _reset: function() | |
306 { | |
307 this._sendRequestRecords = {}; | |
308 this._timerRecords = {}; | |
309 this._requestAnimationFrameRecords = {}; | |
310 this._layoutInvalidate = {}; | |
311 this._lastScheduleStyleRecalculation = {}; | |
312 this._webSocketCreateRecords = {}; | |
313 } | |
314 } | |
315 | |
316 /** | |
317 * @constructor | |
318 * @implements {WebInspector.TimelineModel.Record} | |
319 * @param {!WebInspector.TimelineModel} model | |
320 * @param {!TimelineAgent.TimelineEvent} timelineEvent | |
321 * @param {?WebInspector.TimelineModel.Record} parentRecord | |
322 */ | |
323 WebInspector.TimelineModel.RecordImpl = function(model, timelineEvent, parentRec
ord) | |
324 { | |
325 this._model = model; | |
326 var bindings = this._model._bindings; | |
327 this._record = timelineEvent; | |
328 this._thread = this._record.thread || WebInspector.TimelineModel.MainThreadN
ame; | |
329 this._children = []; | |
330 if (parentRecord) { | |
331 this.parent = parentRecord; | |
332 parentRecord.children().push(this); | |
333 } | |
334 | |
335 this._selfTime = this.endTime() - this.startTime(); | |
336 | |
337 var recordTypes = WebInspector.TimelineModel.RecordType; | |
338 switch (timelineEvent.type) { | |
339 case recordTypes.ResourceSendRequest: | |
340 // Make resource receive record last since request was sent; make finish
record last since response received. | |
341 bindings._sendRequestRecords[timelineEvent.data["requestId"]] = this; | |
342 break; | |
343 | |
344 case recordTypes.ResourceReceiveResponse: | |
345 case recordTypes.ResourceReceivedData: | |
346 case recordTypes.ResourceFinish: | |
347 this._initiator = bindings._sendRequestRecords[timelineEvent.data["reque
stId"]]; | |
348 break; | |
349 | |
350 case recordTypes.TimerInstall: | |
351 bindings._timerRecords[timelineEvent.data["timerId"]] = this; | |
352 break; | |
353 | |
354 case recordTypes.TimerFire: | |
355 this._initiator = bindings._timerRecords[timelineEvent.data["timerId"]]; | |
356 break; | |
357 | |
358 case recordTypes.RequestAnimationFrame: | |
359 bindings._requestAnimationFrameRecords[timelineEvent.data["id"]] = this; | |
360 break; | |
361 | |
362 case recordTypes.FireAnimationFrame: | |
363 this._initiator = bindings._requestAnimationFrameRecords[timelineEvent.d
ata["id"]]; | |
364 break; | |
365 | |
366 case recordTypes.ScheduleStyleRecalculation: | |
367 bindings._lastScheduleStyleRecalculation[this.frameId()] = this; | |
368 break; | |
369 | |
370 case recordTypes.RecalculateStyles: | |
371 this._initiator = bindings._lastScheduleStyleRecalculation[this.frameId(
)]; | |
372 break; | |
373 | |
374 case recordTypes.InvalidateLayout: | |
375 // Consider style recalculation as a reason for layout invalidation, | |
376 // but only if we had no earlier layout invalidation records. | |
377 var layoutInitator = this; | |
378 if (!bindings._layoutInvalidate[this.frameId()] && parentRecord.type() =
== recordTypes.RecalculateStyles) | |
379 layoutInitator = parentRecord._initiator; | |
380 bindings._layoutInvalidate[this.frameId()] = layoutInitator; | |
381 break; | |
382 | |
383 case recordTypes.Layout: | |
384 this._initiator = bindings._layoutInvalidate[this.frameId()]; | |
385 bindings._layoutInvalidate[this.frameId()] = null; | |
386 if (this.stackTrace()) | |
387 this.addWarning(WebInspector.UIString("Forced synchronous layout is
a possible performance bottleneck.")); | |
388 break; | |
389 | |
390 case recordTypes.WebSocketCreate: | |
391 bindings._webSocketCreateRecords[timelineEvent.data["identifier"]] = thi
s; | |
392 break; | |
393 | |
394 case recordTypes.WebSocketSendHandshakeRequest: | |
395 case recordTypes.WebSocketReceiveHandshakeResponse: | |
396 case recordTypes.WebSocketDestroy: | |
397 this._initiator = bindings._webSocketCreateRecords[timelineEvent.data["i
dentifier"]]; | |
398 break; | |
399 } | |
400 } | |
401 | |
402 WebInspector.TimelineModel.RecordImpl.prototype = { | |
403 /** | |
404 * @return {?Array.<!ConsoleAgent.CallFrame>} | |
405 */ | |
406 callSiteStackTrace: function() | |
407 { | |
408 return this._initiator ? this._initiator.stackTrace() : null; | |
409 }, | |
410 | |
411 /** | |
412 * @return {?WebInspector.TimelineModel.Record} | |
413 */ | |
414 initiator: function() | |
415 { | |
416 return this._initiator; | |
417 }, | |
418 | |
419 /** | |
420 * @return {?WebInspector.Target} | |
421 */ | |
422 target: function() | |
423 { | |
424 return this._model._currentTarget; | |
425 }, | |
426 | |
427 /** | |
428 * @return {number} | |
429 */ | |
430 selfTime: function() | |
431 { | |
432 return this._selfTime; | |
433 }, | |
434 | |
435 /** | |
436 * @return {!Array.<!WebInspector.TimelineModel.Record>} | |
437 */ | |
438 children: function() | |
439 { | |
440 return this._children; | |
441 }, | |
442 | |
443 /** | |
444 * @return {number} | |
445 */ | |
446 startTime: function() | |
447 { | |
448 return this._record.startTime; | |
449 }, | |
450 | |
451 /** | |
452 * @return {string} | |
453 */ | |
454 thread: function() | |
455 { | |
456 return this._thread; | |
457 }, | |
458 | |
459 /** | |
460 * @return {number} | |
461 */ | |
462 endTime: function() | |
463 { | |
464 return this._endTime || this._record.endTime || this._record.startTime; | |
465 }, | |
466 | |
467 /** | |
468 * @param {number} endTime | |
469 */ | |
470 setEndTime: function(endTime) | |
471 { | |
472 this._endTime = endTime; | |
473 }, | |
474 | |
475 /** | |
476 * @return {!Object} | |
477 */ | |
478 data: function() | |
479 { | |
480 return this._record.data; | |
481 }, | |
482 | |
483 /** | |
484 * @return {string} | |
485 */ | |
486 type: function() | |
487 { | |
488 return this._record.type; | |
489 }, | |
490 | |
491 /** | |
492 * @return {string} | |
493 */ | |
494 frameId: function() | |
495 { | |
496 return this._record.frameId || ""; | |
497 }, | |
498 | |
499 /** | |
500 * @return {?Array.<!ConsoleAgent.CallFrame>} | |
501 */ | |
502 stackTrace: function() | |
503 { | |
504 if (this._record.stackTrace && this._record.stackTrace.length) | |
505 return this._record.stackTrace; | |
506 return null; | |
507 }, | |
508 | |
509 /** | |
510 * @param {string} key | |
511 * @return {?Object} | |
512 */ | |
513 getUserObject: function(key) | |
514 { | |
515 if (!this._userObjects) | |
516 return null; | |
517 return this._userObjects.get(key); | |
518 }, | |
519 | |
520 /** | |
521 * @param {string} key | |
522 * @param {?Object|undefined} value | |
523 */ | |
524 setUserObject: function(key, value) | |
525 { | |
526 if (!this._userObjects) | |
527 this._userObjects = new Map(); | |
528 this._userObjects.set(key, value); | |
529 }, | |
530 | |
531 /** | |
532 * @param {string} message | |
533 */ | |
534 addWarning: function(message) | |
535 { | |
536 if (!this._warnings) | |
537 this._warnings = []; | |
538 this._warnings.push(message); | |
539 }, | |
540 | |
541 /** | |
542 * @return {?Array.<string>} | |
543 */ | |
544 warnings: function() | |
545 { | |
546 return this._warnings; | |
547 } | |
548 } | |
549 | |
550 /** | |
551 * @constructor | |
552 * @implements {WebInspector.OutputStream} | |
553 * @param {!WebInspector.TimelineModel} model | |
554 * @param {!{cancel: function()}} reader | |
555 * @param {!WebInspector.Progress} progress | |
556 */ | |
557 WebInspector.TimelineModelLoader = function(model, reader, progress) | |
558 { | |
559 this._model = model; | |
560 this._reader = reader; | |
561 this._progress = progress; | |
562 this._buffer = ""; | |
563 this._firstChunk = true; | |
564 } | |
565 | |
566 WebInspector.TimelineModelLoader.prototype = { | |
567 /** | |
568 * @param {string} chunk | |
569 */ | |
570 write: function(chunk) | |
571 { | |
572 var data = this._buffer + chunk; | |
573 var lastIndex = 0; | |
574 var index; | |
575 do { | |
576 index = lastIndex; | |
577 lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, i
ndex); | |
578 } while (lastIndex !== -1) | |
579 | |
580 var json = data.slice(0, index) + "]"; | |
581 this._buffer = data.slice(index); | |
582 | |
583 if (!index) | |
584 return; | |
585 | |
586 if (this._firstChunk) { | |
587 this._firstChunk = false; | |
588 this._model.reset(); | |
589 } else { | |
590 // Prepending "0" to turn string into valid JSON. | |
591 json = "[0" + json; | |
592 } | |
593 | |
594 var items; | |
595 try { | |
596 items = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (JSON.p
arse(json)); | |
597 } catch (e) { | |
598 WebInspector.console.error("Malformed timeline data."); | |
599 this._model.reset(); | |
600 this._reader.cancel(); | |
601 this._progress.done(); | |
602 return; | |
603 } | |
604 | |
605 // Skip 0-th element - it is either version or 0. | |
606 for (var i = 1, size = items.length; i < size; ++i) | |
607 this._model._addRecord(items[i]); | |
608 }, | |
609 | |
610 close: function() | |
611 { | |
612 } | |
613 } | |
614 | |
615 /** | |
616 * @constructor | |
617 * @param {!WebInspector.OutputStream} stream | |
618 */ | |
619 WebInspector.TimelineSaver = function(stream) | |
620 { | |
621 this._stream = stream; | |
622 } | |
623 | |
624 WebInspector.TimelineSaver.prototype = { | |
625 /** | |
626 * @param {!Array.<*>} payloads | |
627 * @param {string} version | |
628 */ | |
629 save: function(payloads, version) | |
630 { | |
631 this._payloads = payloads; | |
632 this._recordIndex = 0; | |
633 this._prologue = "[" + JSON.stringify(version); | |
634 | |
635 this._writeNextChunk(this._stream); | |
636 }, | |
637 | |
638 _writeNextChunk: function(stream) | |
639 { | |
640 const separator = ",\n"; | |
641 var data = []; | |
642 var length = 0; | |
643 | |
644 if (this._prologue) { | |
645 data.push(this._prologue); | |
646 length += this._prologue.length; | |
647 delete this._prologue; | |
648 } else { | |
649 if (this._recordIndex === this._payloads.length) { | |
650 stream.close(); | |
651 return; | |
652 } | |
653 data.push(""); | |
654 } | |
655 while (this._recordIndex < this._payloads.length) { | |
656 var item = JSON.stringify(this._payloads[this._recordIndex]); | |
657 var itemLength = item.length + separator.length; | |
658 if (length + itemLength > WebInspector.TimelineModelImpl.TransferChu
nkLengthBytes) | |
659 break; | |
660 length += itemLength; | |
661 data.push(item); | |
662 ++this._recordIndex; | |
663 } | |
664 if (this._recordIndex === this._payloads.length) | |
665 data.push(data.pop() + "]"); | |
666 stream.write(data.join(separator), this._writeNextChunk.bind(this)); | |
667 } | |
668 } | |
OLD | NEW |