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.
9 * @extends {WebInspector.SDKObject}
11 WebInspector.TracingModel = function(target)
13 WebInspector.SDKObject.call(this, target);
16 InspectorBackend.registerTracingDispatcher(new WebInspector.TracingDispatcher(this));
19 WebInspector.TracingModel.Events = {
20 "BufferUsage": "BufferUsage",
21 "TracingStarted": "TracingStarted",
22 "TracingStopped": "TracingStopped",
23 "TracingComplete": "TracingComplete"
39 WebInspector.TracingModel.EventPayload;
44 WebInspector.TracingModel.Phase = {
64 WebInspector.TracingModel.MetadataEvent = {
65 ProcessSortIndex: "process_sort_index",
66 ProcessName: "process_name",
67 ThreadSortIndex: "thread_sort_index",
68 ThreadName: "thread_name"
71 WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline";
73 WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools";
75 WebInspector.TracingModel.DevToolsMetadataEvent = {
76 TracingStartedInPage: "TracingStartedInPage",
77 TracingStartedInWorker: "TracingStartedInWorker",
80 WebInspector.TracingModel.prototype = {
82 * @return {!Array.<!WebInspector.TracingModel.Event>}
84 devtoolsPageMetadataEvents: function()
86 return this._devtoolsPageMetadataEvents;
90 * @return {!Array.<!WebInspector.TracingModel.Event>}
92 devtoolsWorkerMetadataEvents: function()
94 return this._devtoolsWorkerMetadataEvents;
98 * @param {string} categoryFilter
99 * @param {string} options
100 * @param {function(?string)=} callback
102 start: function(categoryFilter, options, callback)
104 WebInspector.profilingLock().acquire();
106 var bufferUsageReportingIntervalMs = 500;
107 TracingAgent.start(categoryFilter, options, bufferUsageReportingIntervalMs, callback);
115 TracingAgent.end(this._onStop.bind(this));
116 WebInspector.profilingLock().release();
122 sessionId: function()
124 return this._sessionId;
128 * @param {string} sessionId
129 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
131 setEventsForTest: function(sessionId, events)
134 this._sessionId = sessionId;
135 this._eventsCollected(events);
139 * @param {number} usage
141 _bufferUsage: function(usage)
143 this.dispatchEventToListeners(WebInspector.TracingModel.Events.BufferUsage, usage);
147 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
149 _eventsCollected: function(events)
151 for (var i = 0; i < events.length; ++i) {
152 this._addEvent(events[i]);
153 this._rawEvents.push(events[i]);
157 _tracingComplete: function()
159 this._active = false;
160 this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingComplete);
164 * @param {string} sessionId
166 _tracingStarted: function(sessionId)
170 this._sessionId = sessionId;
171 this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingStarted);
176 this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingStopped);
177 this._active = false;
182 this._processById = {};
183 this._minimumRecordTime = 0;
184 this._maximumRecordTime = 0;
185 this._sessionId = null;
186 this._devtoolsPageMetadataEvents = [];
187 this._devtoolsWorkerMetadataEvents = [];
188 this._rawEvents = [];
192 * @return {!Array.<!WebInspector.TracingModel.EventPayload>}
194 rawEvents: function()
196 return this._rawEvents;
200 * @param {!WebInspector.TracingModel.EventPayload} payload
202 _addEvent: function(payload)
204 var process = this._processById[payload.pid];
206 process = new WebInspector.TracingModel.Process(payload.pid);
207 this._processById[payload.pid] = process;
209 var thread = process.threadById(payload.tid);
210 if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) {
211 var timestamp = payload.ts / 1000;
212 // We do allow records for unrelated threads to arrive out-of-order,
213 // so there's a chance we're getting records from the past.
214 if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime))
215 this._minimumRecordTime = timestamp;
216 if (!this._maximumRecordTime || timestamp > this._maximumRecordTime)
217 this._maximumRecordTime = timestamp;
218 var event = thread.addEvent(payload);
219 if (payload.ph === WebInspector.TracingModel.Phase.SnapshotObject)
220 process.addObject(event);
221 if (event && event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage &&
222 event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory &&
223 event.args["sessionId"] === this._sessionId)
224 this._devtoolsPageMetadataEvents.push(event);
225 if (event && event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInWorker &&
226 event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory &&
227 event.args["sessionId"] === this._sessionId)
228 this._devtoolsWorkerMetadataEvents.push(event);
231 switch (payload.name) {
232 case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex:
233 process._setSortIndex(payload.args["sort_index"]);
235 case WebInspector.TracingModel.MetadataEvent.ProcessName:
236 process._setName(payload.args["name"]);
238 case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex:
239 thread._setSortIndex(payload.args["sort_index"]);
241 case WebInspector.TracingModel.MetadataEvent.ThreadName:
242 thread._setName(payload.args["name"]);
250 minimumRecordTime: function()
252 return this._minimumRecordTime;
258 maximumRecordTime: function()
260 return this._maximumRecordTime;
264 * @return {!Array.<!WebInspector.TracingModel.Process>}
266 sortedProcesses: function()
268 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._processById));
271 __proto__: WebInspector.SDKObject.prototype
277 * @param {!WebInspector.TracingModel} tracingModel
279 WebInspector.TracingModel.Loader = function(tracingModel)
281 this._tracingModel = tracingModel;
283 this._sessionIdFound = false;
286 WebInspector.TracingModel.Loader.prototype = {
288 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
290 loadNextChunk: function(events) {
291 if (this._sessionIdFound) {
292 this._tracingModel._eventsCollected(events);
296 var sessionId = null;
297 for (var i = 0, length = events.length; i < length; i++) {
298 var event = events[i];
299 this._events.push(event);
301 if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage &&
302 event.cat.indexOf(WebInspector.TracingModel.DevToolsMetadataEventCategory) !== -1 &&
303 !this._sessionIdFound) {
304 sessionId = event.args["sessionId"];
305 this._sessionIdFound = true;
309 if (this._sessionIdFound) {
310 this._tracingModel._tracingStarted(sessionId);
311 this._tracingModel._eventsCollected(this._events);
317 if (this._sessionIdFound)
318 this._tracingModel._tracingComplete();
320 WebInspector.console.error(WebInspector.UIString("Trace event %s not found while loading tracing model.", WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage));
327 * @param {!WebInspector.TracingModel.EventPayload} payload
328 * @param {number} level
329 * @param {?WebInspector.TracingModel.Thread} thread
331 WebInspector.TracingModel.Event = function(payload, level, thread)
333 this.name = payload.name;
334 this.category = payload.cat;
335 this.startTime = payload.ts / 1000;
337 // Create a new object to avoid modifying original payload which may be saved to file.
339 for (var name in payload.args)
340 this.args[name] = payload.args[name];
342 this.phase = payload.ph;
345 if (typeof payload.dur === "number")
346 this._setEndTime((payload.ts + payload.dur) / 1000);
349 this.id = payload.id;
351 this.thread = thread;
353 /** @type {?string} */
355 /** @type {?WebInspector.TracingModel.Event} */
356 this.initiator = null;
357 /** @type {?Array.<!ConsoleAgent.CallFrame>} */
358 this.stackTrace = null;
359 /** @type {?Element} */
360 this.previewElement = null;
361 /** @type {?string} */
362 this.imageURL = null;
363 /** @type {number} */
364 this.backendNodeId = 0;
366 /** @type {number} */
370 WebInspector.TracingModel.Event.prototype = {
372 * @param {number} endTime
374 _setEndTime: function(endTime)
376 if (endTime < this.startTime) {
377 console.assert(false, "Event out of order: " + this.name);
380 this.endTime = endTime;
381 this.duration = endTime - this.startTime;
385 * @param {!WebInspector.TracingModel.EventPayload} payload
387 _complete: function(payload)
389 if (this.name !== payload.name) {
390 console.assert(false, "Open/close event mismatch: " + this.name + " vs. " + payload.name + " at " + (payload.ts / 1000));
394 for (var name in payload.args) {
395 if (name in this.args)
396 console.error("Same argument name (" + name + ") is used for begin and end phases of " + this.name);
397 this.args[name] = payload.args[name];
400 this._setEndTime(payload.ts / 1000);
405 * @param {!WebInspector.TracingModel.Event} a
406 * @param {!WebInspector.TracingModel.Event} b
409 WebInspector.TracingModel.Event.compareStartTime = function (a, b)
411 return a.startTime - b.startTime;
415 * @param {!WebInspector.TracingModel.Event} a
416 * @param {!WebInspector.TracingModel.Event} b
419 WebInspector.TracingModel.Event.orderedCompareStartTime = function (a, b)
421 // Array.mergeOrdered coalesces objects if comparator returns 0.
422 // To change this behavior this comparator return -1 in the case events
423 // startTime's are equal, so both events got placed into the result array.
424 return a.startTime - b.startTime || -1;
430 WebInspector.TracingModel.NamedObject = function()
434 WebInspector.TracingModel.NamedObject.prototype =
437 * @param {string} name
439 _setName: function(name)
453 * @param {number} sortIndex
455 _setSortIndex: function(sortIndex)
457 this._sortIndex = sortIndex;
462 * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array
464 WebInspector.TracingModel.NamedObject._sort = function(array)
467 * @param {!WebInspector.TracingModel.NamedObject} a
468 * @param {!WebInspector.TracingModel.NamedObject} b
470 function comparator(a, b)
472 return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.name().localeCompare(b.name());
474 return array.sort(comparator);
479 * @extends {WebInspector.TracingModel.NamedObject}
482 WebInspector.TracingModel.Process = function(id)
484 WebInspector.TracingModel.NamedObject.call(this);
485 this._setName("Process " + id);
490 WebInspector.TracingModel.Process.prototype = {
493 * @return {!WebInspector.TracingModel.Thread}
495 threadById: function(id)
497 var thread = this._threads[id];
499 thread = new WebInspector.TracingModel.Thread(this, id);
500 this._threads[id] = thread;
506 * @param {!WebInspector.TracingModel.Event} event
508 addObject: function(event)
510 this.objectsByName(event.name).push(event);
514 * @param {string} name
515 * @return {!Array.<!WebInspector.TracingModel.Event>}
517 objectsByName: function(name)
519 var objects = this._objects[name];
522 this._objects[name] = objects;
528 * @return {!Array.<string>}
530 sortedObjectNames: function()
532 return Object.keys(this._objects).sort();
536 * @return {!Array.<!WebInspector.TracingModel.Thread>}
538 sortedThreads: function()
540 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._threads));
543 __proto__: WebInspector.TracingModel.NamedObject.prototype
548 * @extends {WebInspector.TracingModel.NamedObject}
549 * @param {!WebInspector.TracingModel.Process} process
552 WebInspector.TracingModel.Thread = function(process, id)
554 WebInspector.TracingModel.NamedObject.call(this);
555 this._process = process;
556 this._setName("Thread " + id);
559 this._maxStackDepth = 0;
562 WebInspector.TracingModel.Thread.prototype = {
565 * @return {?WebInspector.Target}
569 //FIXME: correctly specify target
570 return WebInspector.targetManager.targets()[0];
574 * @param {!WebInspector.TracingModel.EventPayload} payload
575 * @return {?WebInspector.TracingModel.Event} event
577 addEvent: function(payload)
579 var timestamp = payload.ts / 1000;
580 for (var top = this._stack.peekLast(); top;) {
581 // For B/E pairs, ignore time and look for top matching B event,
582 // otherwise, only pop event if it's definitely is in the past.
583 if (payload.ph === WebInspector.TracingModel.Phase.End) {
584 if (payload.name === top.name) {
585 top._complete(payload);
589 } else if (top.phase === WebInspector.TracingModel.Phase.Begin || (top.endTime && (top.endTime > timestamp))) {
593 top = this._stack.peekLast();
595 // Quietly ignore unbalanced close events, they're legit (we could have missed start one).
596 if (payload.ph === WebInspector.TracingModel.Phase.End)
599 var event = new WebInspector.TracingModel.Event(payload, this._stack.length, this);
600 if (payload.ph === WebInspector.TracingModel.Phase.Begin || payload.ph === WebInspector.TracingModel.Phase.Complete) {
601 this._stack.push(event);
602 if (this._maxStackDepth < this._stack.length)
603 this._maxStackDepth = this._stack.length;
605 if (this._events.length && this._events.peekLast().startTime > event.startTime)
606 console.assert(false, "Event is our of order: " + event.name);
607 this._events.push(event);
612 * @return {!WebInspector.TracingModel.Process}
616 return this._process;
620 * @return {!Array.<!WebInspector.TracingModel.Event>}
630 maxStackDepth: function()
632 // Reserve one for non-container events.
633 return this._maxStackDepth + 1;
636 __proto__: WebInspector.TracingModel.NamedObject.prototype
642 * @implements {TracingAgent.Dispatcher}
643 * @param {!WebInspector.TracingModel} tracingModel
645 WebInspector.TracingDispatcher = function(tracingModel)
647 this._tracingModel = tracingModel;
650 WebInspector.TracingDispatcher.prototype = {
652 * @param {number} usage
654 bufferUsage: function(usage)
656 this._tracingModel._bufferUsage(usage);
660 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} data
662 dataCollected: function(data)
664 this._tracingModel._eventsCollected(data);
667 tracingComplete: function()
669 this._tracingModel._tracingComplete();
673 * @param {boolean} consoleTimeline
674 * @param {string} sessionId
676 started: function(consoleTimeline, sessionId)
678 this._tracingModel._tracingStarted(sessionId);
683 this._tracingModel._onStop();