2 * Copyright 2014 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
10 WebInspector.TracingModel = function()
18 WebInspector.TracingModel.Phase = {
27 NestableAsyncBegin: "b",
28 NestableAsyncEnd: "e",
29 NestableAsyncInstant: "i",
41 WebInspector.TracingModel.MetadataEvent = {
42 ProcessSortIndex: "process_sort_index",
43 ProcessName: "process_name",
44 ThreadSortIndex: "thread_sort_index",
45 ThreadName: "thread_name"
48 WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline";
50 WebInspector.TracingModel.ConsoleEventCategory = "blink.console";
52 WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools";
54 WebInspector.TracingModel.DevToolsMetadataEvent = {
55 TracingStartedInPage: "TracingStartedInPage",
56 TracingSessionIdForWorker: "TracingSessionIdForWorker",
59 WebInspector.TracingModel._nestableAsyncEventsString =
60 WebInspector.TracingModel.Phase.NestableAsyncBegin +
61 WebInspector.TracingModel.Phase.NestableAsyncEnd +
62 WebInspector.TracingModel.Phase.NestableAsyncInstant;
64 WebInspector.TracingModel._legacyAsyncEventsString =
65 WebInspector.TracingModel.Phase.AsyncBegin +
66 WebInspector.TracingModel.Phase.AsyncEnd +
67 WebInspector.TracingModel.Phase.AsyncStepInto +
68 WebInspector.TracingModel.Phase.AsyncStepPast;
70 WebInspector.TracingModel._asyncEventsString = WebInspector.TracingModel._nestableAsyncEventsString + WebInspector.TracingModel._legacyAsyncEventsString;
73 * @param {string} phase
76 WebInspector.TracingModel.isNestableAsyncPhase = function(phase)
78 return WebInspector.TracingModel._nestableAsyncEventsString.indexOf(phase) >= 0;
82 * @param {string} phase
85 WebInspector.TracingModel.isAsyncBeginPhase = function(phase)
87 return phase === WebInspector.TracingModel.Phase.AsyncBegin || phase === WebInspector.TracingModel.Phase.NestableAsyncBegin;
91 * @param {string} phase
94 WebInspector.TracingModel.isAsyncPhase = function(phase)
96 return WebInspector.TracingModel._asyncEventsString.indexOf(phase) >= 0;
99 WebInspector.TracingModel.prototype = {
101 * @return {!Array.<!WebInspector.TracingModel.Event>}
103 devtoolsPageMetadataEvents: function()
105 return this._devtoolsPageMetadataEvents;
109 * @return {!Array.<!WebInspector.TracingModel.Event>}
111 devtoolsWorkerMetadataEvents: function()
113 return this._devtoolsWorkerMetadataEvents;
119 sessionId: function()
121 return this._sessionId;
125 * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
127 setEventsForTest: function(events)
130 this.addEvents(events);
131 this.tracingComplete();
135 * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
137 addEvents: function(events)
139 for (var i = 0; i < events.length; ++i)
140 this._addEvent(events[i]);
143 tracingComplete: function()
145 this._processMetadataEvents();
146 for (var process in this._processById)
147 this._processById[process]._tracingComplete(this._maximumRecordTime);
148 this._backingStorage.finishWriting(function() {});
153 this._processById = {};
154 this._minimumRecordTime = 0;
155 this._maximumRecordTime = 0;
156 this._sessionId = null;
157 this._devtoolsPageMetadataEvents = [];
158 this._devtoolsWorkerMetadataEvents = [];
159 if (this._backingStorage)
160 this._backingStorage.remove();
161 this._backingStorage = new WebInspector.DeferredTempFile("tracing", String(Date.now()));
162 this._storageOffset = 0;
166 * @param {!WebInspector.OutputStream} outputStream
167 * @param {!WebInspector.OutputStreamDelegate} delegate
169 writeToStream: function(outputStream, delegate)
171 this._backingStorage.writeToOutputStream(outputStream, delegate);
175 * @param {!WebInspector.TracingManager.EventPayload} payload
177 _addEvent: function(payload)
179 var process = this._processById[payload.pid];
181 process = new WebInspector.TracingModel.Process(payload.pid);
182 this._processById[payload.pid] = process;
185 var stringPayload = JSON.stringify(payload);
186 var startOffset = this._storageOffset;
188 var recordDelimiter = ",\n";
189 stringPayload = recordDelimiter + stringPayload;
190 startOffset += recordDelimiter.length;
192 var blob = new Blob([stringPayload]);
193 this._storageOffset += blob.size;
194 this._backingStorage.write([stringPayload]);
196 if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) {
197 var timestamp = payload.ts / 1000;
198 // We do allow records for unrelated threads to arrive out-of-order,
199 // so there's a chance we're getting records from the past.
200 if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime))
201 this._minimumRecordTime = timestamp;
202 var endTimeStamp = (payload.ts + (payload.dur || 0)) / 1000;
203 this._maximumRecordTime = Math.max(this._maximumRecordTime, endTimeStamp);
204 var event = process._addEvent(payload);
207 event._setBackingStorage(this._backingStorage, startOffset, this._storageOffset);
208 if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage &&
209 event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory) {
210 this._devtoolsPageMetadataEvents.push(event);
212 if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingSessionIdForWorker &&
213 event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory) {
214 this._devtoolsWorkerMetadataEvents.push(event);
218 switch (payload.name) {
219 case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex:
220 process._setSortIndex(payload.args["sort_index"]);
222 case WebInspector.TracingModel.MetadataEvent.ProcessName:
223 process._setName(payload.args["name"]);
225 case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex:
226 process.threadById(payload.tid)._setSortIndex(payload.args["sort_index"]);
228 case WebInspector.TracingModel.MetadataEvent.ThreadName:
229 process.threadById(payload.tid)._setName(payload.args["name"]);
234 _processMetadataEvents: function()
236 this._devtoolsPageMetadataEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
237 if (!this._devtoolsPageMetadataEvents.length) {
238 WebInspector.console.error(WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage + " event not found.");
241 var sessionId = this._devtoolsPageMetadataEvents[0].args["sessionId"];
242 this._sessionId = sessionId;
244 var mismatchingIds = {};
245 function checkSessionId(event)
247 var args = event.args;
248 // FIXME: put sessionId into args["data"] for TracingStartedInPage event.
251 var id = args["sessionId"];
252 if (id === sessionId)
254 mismatchingIds[id] = true;
257 this._devtoolsPageMetadataEvents = this._devtoolsPageMetadataEvents.filter(checkSessionId);
258 this._devtoolsWorkerMetadataEvents = this._devtoolsWorkerMetadataEvents.filter(checkSessionId);
260 var idList = Object.keys(mismatchingIds);
262 WebInspector.console.error("Timeline recording was started in more than one page simulaniously. Session id mismatch: " + this._sessionId + " and " + idList + ".");
268 minimumRecordTime: function()
270 return this._minimumRecordTime;
276 maximumRecordTime: function()
278 return this._maximumRecordTime;
282 * @return {!Array.<!WebInspector.TracingModel.Process>}
284 sortedProcesses: function()
286 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._processById));
293 * @param {!WebInspector.TracingModel} tracingModel
295 WebInspector.TracingModel.Loader = function(tracingModel)
297 this._tracingModel = tracingModel;
298 this._firstChunkReceived = false;
301 WebInspector.TracingModel.Loader.prototype = {
303 * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
305 loadNextChunk: function(events)
307 if (!this._firstChunkReceived) {
308 this._tracingModel.reset();
309 this._firstChunkReceived = true;
311 this._tracingModel.addEvents(events);
316 this._tracingModel.tracingComplete();
323 * @param {string} category
324 * @param {string} name
325 * @param {!WebInspector.TracingModel.Phase} phase
326 * @param {number} startTime
327 * @param {!WebInspector.TracingModel.Thread} thread
329 WebInspector.TracingModel.Event = function(category, name, phase, startTime, thread)
331 /** @type {string} */
332 this.category = category;
333 /** @type {string} */
335 /** @type {!WebInspector.TracingModel.Phase} */
337 /** @type {number} */
338 this.startTime = startTime;
339 /** @type {!WebInspector.TracingModel.Thread} */
340 this.thread = thread;
343 /** @type {?string} */
345 /** @type {?WebInspector.TracingModel.Event} */
346 this.initiator = null;
347 /** @type {?Array.<!ConsoleAgent.CallFrame>} */
348 this.stackTrace = null;
349 /** @type {?Element} */
350 this.previewElement = null;
351 /** @type {?string} */
352 this.imageURL = null;
353 /** @type {number} */
354 this.backendNodeId = 0;
356 /** @type {number} */
361 * @param {!WebInspector.TracingManager.EventPayload} payload
362 * @param {!WebInspector.TracingModel.Thread} thread
363 * @return {!WebInspector.TracingModel.Event}
365 WebInspector.TracingModel.Event.fromPayload = function(payload, thread)
367 var event = new WebInspector.TracingModel.Event(payload.cat, payload.name, /** @type {!WebInspector.TracingModel.Phase} */ (payload.ph), payload.ts / 1000, thread);
369 event.addArgs(payload.args);
371 console.error("Missing mandatory event argument 'args' at " + payload.ts / 1000);
372 if (typeof payload.dur === "number")
373 event.setEndTime((payload.ts + payload.dur) / 1000);
375 event.id = payload.id;
379 WebInspector.TracingModel.Event.prototype = {
381 * @param {number} endTime
383 setEndTime: function(endTime)
385 if (endTime < this.startTime) {
386 console.assert(false, "Event out of order: " + this.name);
389 this.endTime = endTime;
390 this.duration = endTime - this.startTime;
394 * @param {!Object} args
396 addArgs: function(args)
398 // Shallow copy args to avoid modifying original payload which may be saved to file.
399 for (var name in args) {
400 if (name in this.args)
401 console.error("Same argument name (" + name + ") is used for begin and end phases of " + this.name);
402 this.args[name] = args[name];
407 * @param {!WebInspector.TracingManager.EventPayload} payload
409 _complete: function(payload)
412 this.addArgs(payload.args);
414 console.error("Missing mandatory event argument 'args' at " + payload.ts / 1000);
415 this.setEndTime(payload.ts / 1000);
419 * @param {!WebInspector.DeferredTempFile} backingFile
420 * @param {number} startOffset
421 * @param {number} endOffset
423 _setBackingStorage: function(backingFile, startOffset, endOffset)
429 * @param {!WebInspector.TracingModel.Event} a
430 * @param {!WebInspector.TracingModel.Event} b
433 WebInspector.TracingModel.Event.compareStartTime = function (a, b)
435 return a.startTime - b.startTime;
439 * @param {!WebInspector.TracingModel.Event} a
440 * @param {!WebInspector.TracingModel.Event} b
443 WebInspector.TracingModel.Event.orderedCompareStartTime = function (a, b)
445 // Array.mergeOrdered coalesces objects if comparator returns 0.
446 // To change this behavior this comparator return -1 in the case events
447 // startTime's are equal, so both events got placed into the result array.
448 return a.startTime - b.startTime || -1;
453 * @extends {WebInspector.TracingModel.Event}
454 * @param {string} category
455 * @param {string} name
456 * @param {number} startTime
457 * @param {!WebInspector.TracingModel.Thread} thread
459 WebInspector.TracingModel.ObjectSnapshot = function(category, name, startTime, thread)
461 WebInspector.TracingModel.Event.call(this, category, name, WebInspector.TracingModel.Phase.SnapshotObject, startTime, thread);
465 * @param {!WebInspector.TracingManager.EventPayload} payload
466 * @param {!WebInspector.TracingModel.Thread} thread
467 * @return {!WebInspector.TracingModel.ObjectSnapshot}
469 WebInspector.TracingModel.ObjectSnapshot.fromPayload = function(payload, thread)
471 var snapshot = new WebInspector.TracingModel.ObjectSnapshot(payload.cat, payload.name, payload.ts / 1000, thread);
473 snapshot.id = payload.id;
474 if (!payload.args || !payload.args["snapshot"]) {
475 console.error("Missing mandatory 'snapshot' argument at " + payload.ts / 1000);
479 snapshot.addArgs(payload.args);
483 WebInspector.TracingModel.ObjectSnapshot.prototype = {
485 * @param {function(?Object)} callback
487 requestObject: function(callback)
489 var snapshot = this.args["snapshot"];
494 this._file.readRange(this._startOffset, this._endOffset, onRead);
496 * @param {?string} result
498 function onRead(result)
506 var payload = JSON.parse(result);
507 snapshot = payload["args"]["snapshot"];
509 WebInspector.console.error("Malformed event data in backing storage");
516 * @param {!WebInspector.DeferredTempFile} backingFile
517 * @param {number} startOffset
518 * @param {number} endOffset
521 _setBackingStorage: function(backingFile, startOffset, endOffset)
523 if (endOffset - startOffset < 10000)
525 this._file = backingFile;
526 this._startOffset = startOffset;
527 this._endOffset = endOffset;
531 __proto__: WebInspector.TracingModel.Event.prototype
538 WebInspector.TracingModel.NamedObject = function()
542 WebInspector.TracingModel.NamedObject.prototype =
545 * @param {string} name
547 _setName: function(name)
561 * @param {number} sortIndex
563 _setSortIndex: function(sortIndex)
565 this._sortIndex = sortIndex;
570 * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array
572 WebInspector.TracingModel.NamedObject._sort = function(array)
575 * @param {!WebInspector.TracingModel.NamedObject} a
576 * @param {!WebInspector.TracingModel.NamedObject} b
578 function comparator(a, b)
580 return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.name().localeCompare(b.name());
582 return array.sort(comparator);
587 * @extends {WebInspector.TracingModel.NamedObject}
590 WebInspector.TracingModel.Process = function(id)
592 WebInspector.TracingModel.NamedObject.call(this);
593 this._setName("Process " + id);
597 /** @type {!Array.<!WebInspector.TracingModel.Event>} */
598 this._asyncEvents = [];
599 /** @type {!Object.<string, ?Array.<!WebInspector.TracingModel.Event>>} */
600 this._openAsyncEvents = {};
601 /** @type {!Object.<string, !Array.<!WebInspector.TracingModel.Event>>} */
602 this._openNestableAsyncEvents = {};
605 WebInspector.TracingModel.Process.prototype = {
616 * @return {!WebInspector.TracingModel.Thread}
618 threadById: function(id)
620 var thread = this._threads[id];
622 thread = new WebInspector.TracingModel.Thread(this, id);
623 this._threads[id] = thread;
629 * @param {!WebInspector.TracingManager.EventPayload} payload
630 * @return {?WebInspector.TracingModel.Event} event
632 _addEvent: function(payload)
634 var phase = WebInspector.TracingModel.Phase;
636 var event = this.threadById(payload.tid)._addEvent(payload);
639 // Build async event when we've got events from all threads, so we can sort them and process in the chronological order.
640 // However, also add individual async events to the thread flow (above), so we can easily display them on the same chart as
641 // other events, should we choose so.
642 if (WebInspector.TracingModel.isAsyncPhase(payload.ph))
643 this._asyncEvents.push(event);
644 if (payload.ph === phase.SnapshotObject)
645 this.objectsByName(event.name).push(event);
650 * @param {!number} lastEventTimeMs
652 _tracingComplete: function(lastEventTimeMs)
654 this._asyncEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
655 for (var i = 0; i < this._asyncEvents.length; ++i) {
656 var event = this._asyncEvents[i];
657 if (WebInspector.TracingModel.isNestableAsyncPhase(event.phase))
658 this._addNestableAsyncEvent(event);
660 this._addAsyncEvent(event);
663 for (var key in this._openAsyncEvents) {
664 var steps = this._openAsyncEvents[key];
667 var startEvent = steps[0];
668 var syntheticEndEvent = new WebInspector.TracingModel.Event(startEvent.category, startEvent.name, WebInspector.TracingModel.Phase.AsyncEnd, lastEventTimeMs, startEvent.thread);
669 steps.push(syntheticEndEvent);
670 startEvent.setEndTime(lastEventTimeMs)
672 for (var key in this._openNestableAsyncEvents) {
673 var openEvents = this._openNestableAsyncEvents[key];
674 while (openEvents.length)
675 openEvents.pop().setEndTime(lastEventTimeMs);
677 this._asyncEvents = [];
678 this._openAsyncEvents = {};
679 this._openNestableAsyncEvents = {};
683 * @param {!WebInspector.TracingModel.Event} event
685 _addNestableAsyncEvent: function(event)
687 var phase = WebInspector.TracingModel.Phase;
688 var key = event.category + "." + event.id;
689 var openEventsStack = this._openNestableAsyncEvents[key];
691 switch (event.phase) {
692 case phase.NestableAsyncBegin:
693 if (!openEventsStack) {
694 openEventsStack = [];
695 this._openNestableAsyncEvents[key] = openEventsStack;
697 openEventsStack.push(event);
698 // fall-through intended
699 case phase.NestableAsyncInstant:
700 event.thread._addAsyncEventSteps([event]);
702 case phase.NestableAsyncEnd:
703 if (!openEventsStack)
705 var top = openEventsStack.pop();
706 if (top.name !== event.name) {
707 console.error("Begin/end event mismatch for nestable async event, " + top.name + " vs. " + event.name);
710 top.setEndTime(event.startTime);
715 * @param {!WebInspector.TracingModel.Event} event
717 _addAsyncEvent: function(event)
719 var phase = WebInspector.TracingModel.Phase;
720 var key = event.category + "." + event.name + "." + event.id;
721 var steps = this._openAsyncEvents[key];
723 if (event.phase === phase.AsyncBegin) {
725 console.error("Event " + event.name + " has already been started");
729 this._openAsyncEvents[key] = steps;
730 event.thread._addAsyncEventSteps(steps);
734 console.error("Unexpected async event " + event.name + ", phase " + event.phase);
737 if (event.phase === phase.AsyncEnd) {
739 steps[0].setEndTime(event.startTime);
740 delete this._openAsyncEvents[key];
741 } else if (event.phase === phase.AsyncStepInto || event.phase === phase.AsyncStepPast) {
742 var lastPhase = steps.peekLast().phase;
743 if (lastPhase !== phase.AsyncBegin && lastPhase !== event.phase) {
744 console.assert(false, "Async event step phase mismatch: " + lastPhase + " at " + steps.peekLast().startTime + " vs. " + event.phase + " at " + event.startTime);
749 console.assert(false, "Invalid async event phase");
754 * @param {string} name
755 * @return {!Array.<!WebInspector.TracingModel.Event>}
757 objectsByName: function(name)
759 var objects = this._objects[name];
762 this._objects[name] = objects;
768 * @return {!Array.<string>}
770 sortedObjectNames: function()
772 return Object.keys(this._objects).sort();
776 * @return {!Array.<!WebInspector.TracingModel.Thread>}
778 sortedThreads: function()
780 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._threads));
783 __proto__: WebInspector.TracingModel.NamedObject.prototype
788 * @extends {WebInspector.TracingModel.NamedObject}
789 * @param {!WebInspector.TracingModel.Process} process
792 WebInspector.TracingModel.Thread = function(process, id)
794 WebInspector.TracingModel.NamedObject.call(this);
795 this._process = process;
796 this._setName("Thread " + id);
798 this._asyncEvents = [];
804 WebInspector.TracingModel.Thread.prototype = {
806 * @return {?WebInspector.Target}
810 //FIXME: correctly specify target
811 return WebInspector.targetManager.targets()[0] || null;
815 * @param {!WebInspector.TracingManager.EventPayload} payload
816 * @return {?WebInspector.TracingModel.Event} event
818 _addEvent: function(payload)
820 var timestamp = payload.ts / 1000;
821 if (payload.ph === WebInspector.TracingModel.Phase.End) {
822 // Quietly ignore unbalanced close events, they're legit (we could have missed start one).
823 if (!this._stack.length)
825 var top = this._stack.pop();
826 if (top.name !== payload.name || top.category !== payload.cat)
827 console.error("B/E events mismatch at " + top.startTime + " (" + top.name + ") vs. " + timestamp + " (" + payload.name + ")");
829 top._complete(payload);
832 var event = payload.ph === WebInspector.TracingModel.Phase.SnapshotObject
833 ? WebInspector.TracingModel.ObjectSnapshot.fromPayload(payload, this)
834 : WebInspector.TracingModel.Event.fromPayload(payload, this);
835 if (payload.ph === WebInspector.TracingModel.Phase.Begin)
836 this._stack.push(event);
837 if (this._events.length && this._events.peekLast().startTime > event.startTime)
838 console.assert(false, "Event is out of order: " + event.name);
839 this._events.push(event);
844 * @param {!Array.<!WebInspector.TracingModel.Event>} eventSteps
846 _addAsyncEventSteps: function(eventSteps)
848 this._asyncEvents.push(eventSteps);
860 * @return {!WebInspector.TracingModel.Process}
864 return this._process;
868 * @return {!Array.<!WebInspector.TracingModel.Event>}
876 * @return {!Array.<!WebInspector.TracingModel.Event>}
878 asyncEvents: function()
880 return this._asyncEvents;
883 __proto__: WebInspector.TracingModel.NamedObject.prototype