| /* |
| * Copyright 2014 The Chromium Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.SDKObject} |
| */ |
| WebInspector.TracingModel = function(target) |
| { |
| WebInspector.SDKObject.call(this, target); |
| this.reset(); |
| this._active = false; |
| InspectorBackend.registerTracingDispatcher(new WebInspector.TracingDispatcher(this)); |
| } |
| |
| WebInspector.TracingModel.Events = { |
| "BufferUsage": "BufferUsage", |
| "TracingStarted": "TracingStarted", |
| "TracingStopped": "TracingStopped", |
| "TracingComplete": "TracingComplete" |
| } |
| |
| /** @typedef {!{ |
| cat: string, |
| pid: number, |
| tid: number, |
| ts: number, |
| ph: string, |
| name: string, |
| args: !Object, |
| dur: number, |
| id: number, |
| s: string |
| }} |
| */ |
| WebInspector.TracingModel.EventPayload; |
| |
| /** |
| * @enum {string} |
| */ |
| WebInspector.TracingModel.Phase = { |
| Begin: "B", |
| End: "E", |
| Complete: "X", |
| Instant: "i", |
| AsyncBegin: "S", |
| AsyncStepInto: "T", |
| AsyncStepPast: "p", |
| AsyncEnd: "F", |
| FlowBegin: "s", |
| FlowStep: "t", |
| FlowEnd: "f", |
| Metadata: "M", |
| Counter: "C", |
| Sample: "P", |
| CreateObject: "N", |
| SnapshotObject: "O", |
| DeleteObject: "D" |
| }; |
| |
| WebInspector.TracingModel.MetadataEvent = { |
| ProcessSortIndex: "process_sort_index", |
| ProcessName: "process_name", |
| ThreadSortIndex: "thread_sort_index", |
| ThreadName: "thread_name" |
| } |
| |
| WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline"; |
| |
| WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools"; |
| |
| WebInspector.TracingModel.DevToolsMetadataEvent = { |
| TracingStartedInPage: "TracingStartedInPage", |
| TracingStartedInWorker: "TracingStartedInWorker", |
| }; |
| |
| WebInspector.TracingModel.prototype = { |
| /** |
| * @return {!Array.<!WebInspector.TracingModel.Event>} |
| */ |
| devtoolsPageMetadataEvents: function() |
| { |
| return this._devtoolsPageMetadataEvents; |
| }, |
| |
| /** |
| * @return {!Array.<!WebInspector.TracingModel.Event>} |
| */ |
| devtoolsWorkerMetadataEvents: function() |
| { |
| return this._devtoolsWorkerMetadataEvents; |
| }, |
| |
| /** |
| * @param {string} categoryFilter |
| * @param {string} options |
| * @param {function(?string)=} callback |
| */ |
| start: function(categoryFilter, options, callback) |
| { |
| WebInspector.profilingLock().acquire(); |
| this.reset(); |
| var bufferUsageReportingIntervalMs = 500; |
| TracingAgent.start(categoryFilter, options, bufferUsageReportingIntervalMs, callback); |
| this._active = true; |
| }, |
| |
| stop: function() |
| { |
| if (!this._active) |
| return; |
| TracingAgent.end(this._onStop.bind(this)); |
| WebInspector.profilingLock().release(); |
| }, |
| |
| /** |
| * @return {?string} |
| */ |
| sessionId: function() |
| { |
| return this._sessionId; |
| }, |
| |
| /** |
| * @param {string} sessionId |
| * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events |
| */ |
| setEventsForTest: function(sessionId, events) |
| { |
| this.reset(); |
| this._sessionId = sessionId; |
| this._eventsCollected(events); |
| }, |
| |
| /** |
| * @param {number} usage |
| */ |
| _bufferUsage: function(usage) |
| { |
| this.dispatchEventToListeners(WebInspector.TracingModel.Events.BufferUsage, usage); |
| }, |
| |
| /** |
| * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events |
| */ |
| _eventsCollected: function(events) |
| { |
| for (var i = 0; i < events.length; ++i) { |
| this._addEvent(events[i]); |
| this._rawEvents.push(events[i]); |
| } |
| }, |
| |
| _tracingComplete: function() |
| { |
| this._active = false; |
| this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingComplete); |
| }, |
| |
| /** |
| * @param {string} sessionId |
| */ |
| _tracingStarted: function(sessionId) |
| { |
| this.reset(); |
| this._active = true; |
| this._sessionId = sessionId; |
| this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingStarted); |
| }, |
| |
| _onStop: function() |
| { |
| this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingStopped); |
| this._active = false; |
| }, |
| |
| reset: function() |
| { |
| this._processById = {}; |
| this._minimumRecordTime = 0; |
| this._maximumRecordTime = 0; |
| this._sessionId = null; |
| this._devtoolsPageMetadataEvents = []; |
| this._devtoolsWorkerMetadataEvents = []; |
| this._rawEvents = []; |
| }, |
| |
| /** |
| * @return {!Array.<!WebInspector.TracingModel.EventPayload>} |
| */ |
| rawEvents: function() |
| { |
| return this._rawEvents; |
| }, |
| |
| /** |
| * @param {!WebInspector.TracingModel.EventPayload} payload |
| */ |
| _addEvent: function(payload) |
| { |
| var process = this._processById[payload.pid]; |
| if (!process) { |
| process = new WebInspector.TracingModel.Process(payload.pid); |
| this._processById[payload.pid] = process; |
| } |
| var thread = process.threadById(payload.tid); |
| if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) { |
| var timestamp = payload.ts / 1000; |
| // We do allow records for unrelated threads to arrive out-of-order, |
| // so there's a chance we're getting records from the past. |
| if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime)) |
| this._minimumRecordTime = timestamp; |
| if (!this._maximumRecordTime || timestamp > this._maximumRecordTime) |
| this._maximumRecordTime = timestamp; |
| var event = thread.addEvent(payload); |
| if (payload.ph === WebInspector.TracingModel.Phase.SnapshotObject) |
| process.addObject(event); |
| if (event && event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage && |
| event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory && |
| event.args["sessionId"] === this._sessionId) |
| this._devtoolsPageMetadataEvents.push(event); |
| if (event && event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInWorker && |
| event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory && |
| event.args["sessionId"] === this._sessionId) |
| this._devtoolsWorkerMetadataEvents.push(event); |
| return; |
| } |
| switch (payload.name) { |
| case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex: |
| process._setSortIndex(payload.args["sort_index"]); |
| break; |
| case WebInspector.TracingModel.MetadataEvent.ProcessName: |
| process._setName(payload.args["name"]); |
| break; |
| case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex: |
| thread._setSortIndex(payload.args["sort_index"]); |
| break; |
| case WebInspector.TracingModel.MetadataEvent.ThreadName: |
| thread._setName(payload.args["name"]); |
| break; |
| } |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| minimumRecordTime: function() |
| { |
| return this._minimumRecordTime; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| maximumRecordTime: function() |
| { |
| return this._maximumRecordTime; |
| }, |
| |
| /** |
| * @return {!Array.<!WebInspector.TracingModel.Process>} |
| */ |
| sortedProcesses: function() |
| { |
| return WebInspector.TracingModel.NamedObject._sort(Object.values(this._processById)); |
| }, |
| |
| __proto__: WebInspector.SDKObject.prototype |
| } |
| |
| |
| /** |
| * @constructor |
| * @param {!WebInspector.TracingModel} tracingModel |
| */ |
| WebInspector.TracingModel.Loader = function(tracingModel) |
| { |
| this._tracingModel = tracingModel; |
| this._events = []; |
| this._sessionIdFound = false; |
| } |
| |
| WebInspector.TracingModel.Loader.prototype = { |
| /** |
| * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events |
| */ |
| loadNextChunk: function(events) { |
| if (this._sessionIdFound) { |
| this._tracingModel._eventsCollected(events); |
| return; |
| } |
| |
| var sessionId = null; |
| for (var i = 0, length = events.length; i < length; i++) { |
| var event = events[i]; |
| this._events.push(event); |
| |
| if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage && |
| event.cat.indexOf(WebInspector.TracingModel.DevToolsMetadataEventCategory) !== -1 && |
| !this._sessionIdFound) { |
| sessionId = event.args["sessionId"]; |
| this._sessionIdFound = true; |
| } |
| } |
| |
| if (this._sessionIdFound) { |
| this._tracingModel._tracingStarted(sessionId); |
| this._tracingModel._eventsCollected(this._events); |
| } |
| }, |
| |
| finish: function() |
| { |
| if (this._sessionIdFound) |
| this._tracingModel._tracingComplete(); |
| else |
| WebInspector.console.error(WebInspector.UIString("Trace event %s not found while loading tracing model.", WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage)); |
| } |
| } |
| |
| |
| /** |
| * @constructor |
| * @param {!WebInspector.TracingModel.EventPayload} payload |
| * @param {number} level |
| * @param {?WebInspector.TracingModel.Thread} thread |
| */ |
| WebInspector.TracingModel.Event = function(payload, level, thread) |
| { |
| this.name = payload.name; |
| this.category = payload.cat; |
| this.startTime = payload.ts / 1000; |
| if (payload.args) { |
| // Create a new object to avoid modifying original payload which may be saved to file. |
| this.args = {}; |
| for (var name in payload.args) |
| this.args[name] = payload.args[name]; |
| } |
| this.phase = payload.ph; |
| this.level = level; |
| |
| if (typeof payload.dur === "number") |
| this._setEndTime((payload.ts + payload.dur) / 1000); |
| |
| if (payload.id) |
| this.id = payload.id; |
| |
| this.thread = thread; |
| |
| /** @type {?string} */ |
| this.warning = null; |
| /** @type {?WebInspector.TracingModel.Event} */ |
| this.initiator = null; |
| /** @type {?Array.<!ConsoleAgent.CallFrame>} */ |
| this.stackTrace = null; |
| /** @type {?Element} */ |
| this.previewElement = null; |
| /** @type {?string} */ |
| this.imageURL = null; |
| /** @type {number} */ |
| this.backendNodeId = 0; |
| |
| /** @type {number} */ |
| this.selfTime = 0; |
| } |
| |
| WebInspector.TracingModel.Event.prototype = { |
| /** |
| * @param {number} endTime |
| */ |
| _setEndTime: function(endTime) |
| { |
| if (endTime < this.startTime) { |
| console.assert(false, "Event out of order: " + this.name); |
| return; |
| } |
| this.endTime = endTime; |
| this.duration = endTime - this.startTime; |
| }, |
| |
| /** |
| * @param {!WebInspector.TracingModel.EventPayload} payload |
| */ |
| _complete: function(payload) |
| { |
| if (this.name !== payload.name) { |
| console.assert(false, "Open/close event mismatch: " + this.name + " vs. " + payload.name + " at " + (payload.ts / 1000)); |
| return; |
| } |
| if (payload.args) { |
| for (var name in payload.args) { |
| if (name in this.args) |
| console.error("Same argument name (" + name + ") is used for begin and end phases of " + this.name); |
| this.args[name] = payload.args[name]; |
| } |
| } |
| this._setEndTime(payload.ts / 1000); |
| } |
| } |
| |
| /** |
| * @param {!WebInspector.TracingModel.Event} a |
| * @param {!WebInspector.TracingModel.Event} b |
| * @return {number} |
| */ |
| WebInspector.TracingModel.Event.compareStartTime = function (a, b) |
| { |
| return a.startTime - b.startTime; |
| } |
| |
| /** |
| * @param {!WebInspector.TracingModel.Event} a |
| * @param {!WebInspector.TracingModel.Event} b |
| * @return {number} |
| */ |
| WebInspector.TracingModel.Event.orderedCompareStartTime = function (a, b) |
| { |
| // Array.mergeOrdered coalesces objects if comparator returns 0. |
| // To change this behavior this comparator return -1 in the case events |
| // startTime's are equal, so both events got placed into the result array. |
| return a.startTime - b.startTime || -1; |
| } |
| |
| /** |
| * @constructor |
| */ |
| WebInspector.TracingModel.NamedObject = function() |
| { |
| } |
| |
| WebInspector.TracingModel.NamedObject.prototype = |
| { |
| /** |
| * @param {string} name |
| */ |
| _setName: function(name) |
| { |
| this._name = name; |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| name: function() |
| { |
| return this._name; |
| }, |
| |
| /** |
| * @param {number} sortIndex |
| */ |
| _setSortIndex: function(sortIndex) |
| { |
| this._sortIndex = sortIndex; |
| }, |
| } |
| |
| /** |
| * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array |
| */ |
| WebInspector.TracingModel.NamedObject._sort = function(array) |
| { |
| /** |
| * @param {!WebInspector.TracingModel.NamedObject} a |
| * @param {!WebInspector.TracingModel.NamedObject} b |
| */ |
| function comparator(a, b) |
| { |
| return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.name().localeCompare(b.name()); |
| } |
| return array.sort(comparator); |
| } |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.TracingModel.NamedObject} |
| * @param {number} id |
| */ |
| WebInspector.TracingModel.Process = function(id) |
| { |
| WebInspector.TracingModel.NamedObject.call(this); |
| this._setName("Process " + id); |
| this._threads = {}; |
| this._objects = {}; |
| } |
| |
| WebInspector.TracingModel.Process.prototype = { |
| /** |
| * @param {number} id |
| * @return {!WebInspector.TracingModel.Thread} |
| */ |
| threadById: function(id) |
| { |
| var thread = this._threads[id]; |
| if (!thread) { |
| thread = new WebInspector.TracingModel.Thread(this, id); |
| this._threads[id] = thread; |
| } |
| return thread; |
| }, |
| |
| /** |
| * @param {!WebInspector.TracingModel.Event} event |
| */ |
| addObject: function(event) |
| { |
| this.objectsByName(event.name).push(event); |
| }, |
| |
| /** |
| * @param {string} name |
| * @return {!Array.<!WebInspector.TracingModel.Event>} |
| */ |
| objectsByName: function(name) |
| { |
| var objects = this._objects[name]; |
| if (!objects) { |
| objects = []; |
| this._objects[name] = objects; |
| } |
| return objects; |
| }, |
| |
| /** |
| * @return {!Array.<string>} |
| */ |
| sortedObjectNames: function() |
| { |
| return Object.keys(this._objects).sort(); |
| }, |
| |
| /** |
| * @return {!Array.<!WebInspector.TracingModel.Thread>} |
| */ |
| sortedThreads: function() |
| { |
| return WebInspector.TracingModel.NamedObject._sort(Object.values(this._threads)); |
| }, |
| |
| __proto__: WebInspector.TracingModel.NamedObject.prototype |
| } |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.TracingModel.NamedObject} |
| * @param {!WebInspector.TracingModel.Process} process |
| * @param {number} id |
| */ |
| WebInspector.TracingModel.Thread = function(process, id) |
| { |
| WebInspector.TracingModel.NamedObject.call(this); |
| this._process = process; |
| this._setName("Thread " + id); |
| this._events = []; |
| this._stack = []; |
| this._maxStackDepth = 0; |
| } |
| |
| WebInspector.TracingModel.Thread.prototype = { |
| |
| /** |
| * @return {?WebInspector.Target} |
| */ |
| target: function() |
| { |
| //FIXME: correctly specify target |
| return WebInspector.targetManager.targets()[0]; |
| }, |
| |
| /** |
| * @param {!WebInspector.TracingModel.EventPayload} payload |
| * @return {?WebInspector.TracingModel.Event} event |
| */ |
| addEvent: function(payload) |
| { |
| var timestamp = payload.ts / 1000; |
| for (var top = this._stack.peekLast(); top;) { |
| // For B/E pairs, ignore time and look for top matching B event, |
| // otherwise, only pop event if it's definitely is in the past. |
| if (payload.ph === WebInspector.TracingModel.Phase.End) { |
| if (payload.name === top.name) { |
| top._complete(payload); |
| this._stack.pop(); |
| return null; |
| } |
| } else if (top.phase === WebInspector.TracingModel.Phase.Begin || (top.endTime && (top.endTime > timestamp))) { |
| break; |
| } |
| this._stack.pop(); |
| top = this._stack.peekLast(); |
| } |
| // Quietly ignore unbalanced close events, they're legit (we could have missed start one). |
| if (payload.ph === WebInspector.TracingModel.Phase.End) |
| return null; |
| |
| var event = new WebInspector.TracingModel.Event(payload, this._stack.length, this); |
| if (payload.ph === WebInspector.TracingModel.Phase.Begin || payload.ph === WebInspector.TracingModel.Phase.Complete) { |
| this._stack.push(event); |
| if (this._maxStackDepth < this._stack.length) |
| this._maxStackDepth = this._stack.length; |
| } |
| if (this._events.length && this._events.peekLast().startTime > event.startTime) |
| console.assert(false, "Event is our of order: " + event.name); |
| this._events.push(event); |
| return event; |
| }, |
| |
| /** |
| * @return {!WebInspector.TracingModel.Process} |
| */ |
| process: function() |
| { |
| return this._process; |
| }, |
| |
| /** |
| * @return {!Array.<!WebInspector.TracingModel.Event>} |
| */ |
| events: function() |
| { |
| return this._events; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| maxStackDepth: function() |
| { |
| // Reserve one for non-container events. |
| return this._maxStackDepth + 1; |
| }, |
| |
| __proto__: WebInspector.TracingModel.NamedObject.prototype |
| } |
| |
| |
| /** |
| * @constructor |
| * @implements {TracingAgent.Dispatcher} |
| * @param {!WebInspector.TracingModel} tracingModel |
| */ |
| WebInspector.TracingDispatcher = function(tracingModel) |
| { |
| this._tracingModel = tracingModel; |
| } |
| |
| WebInspector.TracingDispatcher.prototype = { |
| /** |
| * @param {number} usage |
| */ |
| bufferUsage: function(usage) |
| { |
| this._tracingModel._bufferUsage(usage); |
| }, |
| |
| /** |
| * @param {!Array.<!WebInspector.TracingModel.EventPayload>} data |
| */ |
| dataCollected: function(data) |
| { |
| this._tracingModel._eventsCollected(data); |
| }, |
| |
| tracingComplete: function() |
| { |
| this._tracingModel._tracingComplete(); |
| }, |
| |
| /** |
| * @param {boolean} consoleTimeline |
| * @param {string} sessionId |
| */ |
| started: function(consoleTimeline, sessionId) |
| { |
| this._tracingModel._tracingStarted(sessionId); |
| }, |
| |
| stopped: function() |
| { |
| this._tracingModel._onStop(); |
| } |
| } |