Chromium Code Reviews| Index: Source/devtools/front_end/TracingAgent.js |
| diff --git a/Source/devtools/front_end/TracingAgent.js b/Source/devtools/front_end/TracingAgent.js |
| index e7ad664f91a48cede2ca1a3581f2773cd58adfe4..9a1f80dd7c21815fec22d7f37e6925327732f077 100644 |
| --- a/Source/devtools/front_end/TracingAgent.js |
| +++ b/Source/devtools/front_end/TracingAgent.js |
| @@ -30,13 +30,64 @@ |
| /** |
| * @constructor |
| + * @extends {WebInspector.Object} |
| */ |
| WebInspector.TracingAgent = function() |
| { |
| + WebInspector.Object.call(this); |
| this._active = false; |
| InspectorBackend.registerTracingDispatcher(new WebInspector.TracingDispatcher(this)); |
| } |
| +WebInspector.TracingAgent.Events = { |
| + EventsCollected: "EventsCollected" |
| +}; |
| + |
| +/** @typedef {!{ |
| + cat: string, |
| + pid: number, |
| + tid: number, |
| + ts: number, |
| + ph: string, |
| + name: string, |
| + args: !Object, |
| + dur: number, |
| + id: number, |
| + s: string |
| + }} |
| + */ |
| +WebInspector.TracingAgent.Event; |
| + |
| +/** |
| + * @enum {string} |
| + */ |
| +WebInspector.TracingAgent.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.TracingAgent.MetadataEvent = { |
| + ProcessSortIndex: "process_sort_index", |
| + ProcessName: "process_name", |
| + ThreadSortIndex: "thread_sort_index", |
| + ThreadName: "thread_name" |
| +} |
| + |
| WebInspector.TracingAgent.prototype = { |
| /** |
| * @param {string} categoryPatterns |
| @@ -47,7 +98,6 @@ WebInspector.TracingAgent.prototype = { |
| { |
| TracingAgent.start(categoryPatterns, options, callback); |
| this._active = true; |
| - this._events = []; |
| }, |
| /** |
| @@ -63,27 +113,21 @@ WebInspector.TracingAgent.prototype = { |
| TracingAgent.end(); |
| }, |
| - /** |
| - * @return {!Array.<!{cat: string, args: !Object, ph: string, ts: number}>} |
| - */ |
| - events: function() |
| - { |
| - return this._events; |
| - }, |
| - |
| _eventsCollected: function(events) |
| { |
| - Array.prototype.push.apply(this._events, events); |
| + this.dispatchEventToListeners(WebInspector.TracingAgent.Events.EventsCollected, events); |
| }, |
| _tracingComplete: function() |
| { |
| this._active = false; |
| - if (this._pendingStopCallback) { |
| - this._pendingStopCallback(); |
| - this._pendingStopCallback = null; |
| - } |
| - } |
| + if (!this._pendingStopCallback) |
| + return; |
| + this._pendingStopCallback(); |
| + this._pendingStopCallback = null; |
| + }, |
| + |
| + __proto__: WebInspector.Object.prototype |
| } |
| /** |
| @@ -112,3 +156,315 @@ WebInspector.TracingDispatcher.prototype = { |
| * @type {!WebInspector.TracingAgent} |
| */ |
| WebInspector.tracingAgent; |
| + |
| +/** |
| + * @constructor |
| + */ |
| +WebInspector.TracingModel = function() |
| +{ |
| + this.reset(); |
| +} |
| + |
| +WebInspector.TracingModel.prototype = { |
| + reset: function() |
| + { |
| + this._processById = {}; |
| + this._minimumRecordTime = null; |
| + this._maximumRecordTime = null; |
| + }, |
| + |
| + /** |
| + * @param {!Array.<!WebInspector.TracingAgent.Event>} payload |
| + */ |
| + addEvents: function(payload) |
| + { |
| + for (var i = 0; i < payload.length; ++i) |
| + this.addEvent(payload[i]); |
| + }, |
| + |
| + /** |
| + * @param {!WebInspector.TracingAgent.Event} 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.TracingAgent.Phase.Metadata) { |
| + var timestamp = payload.ts; |
| + // 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; |
| + thread.addEvent(payload); |
| + return; |
| + } |
| + switch (payload.name) { |
| + case WebInspector.TracingAgent.MetadataEvent.ProcessSortIndex: |
| + process._setSortIndex(payload.args["sort_index"]); |
| + break; |
| + case WebInspector.TracingAgent.MetadataEvent.ProcessName: |
| + process._setName(payload.args["name"]); |
| + break; |
| + case WebInspector.TracingAgent.MetadataEvent.ThreadSortIndex: |
| + thread._setSortIndex(payload.args["sort_index"]); |
| + break; |
| + case WebInspector.TracingAgent.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)); |
| + } |
| +} |
| + |
| +/** |
| + * @constructor |
| + * @param {!WebInspector.TracingAgent.Event} payload |
| + * @param {number} level |
| + */ |
| +WebInspector.TracingModel.Event = function(payload, level) |
|
pfeldman
2014/03/26 20:10:06
Can we actually afford keeping both - payload and
caseq
2014/03/28 17:13:41
Dropped payload, we just shallow-copy properties o
|
| +{ |
| + this._payload = payload; |
| + this.level = level; |
| +} |
| + |
| +WebInspector.TracingModel.Event.prototype = { |
| + /** |
| + * @return {string} |
| + */ |
| + get name() |
|
pfeldman
2014/03/26 18:32:09
no getters please.
caseq
2014/03/28 17:13:41
Gone with the above change.
|
| + { |
| + return this._payload.name; |
| + }, |
| + |
| + /** |
| + * @return {number} |
| + */ |
| + get startTime() |
| + { |
| + return this._payload.ts; |
| + }, |
| + |
| + /** |
| + * @return {!Object} |
| + */ |
| + get args() |
| + { |
| + return this._payload.args; |
| + }, |
| + |
| + /** |
| + * @return {string} |
| + */ |
| + get phase() |
| + { |
| + return this._payload.ph; |
| + }, |
| + |
| + /** |
| + * @param {number} duration |
| + */ |
| + _setDuration: function(duration) |
| + { |
| + this.endTime = this.startTime + duration; |
| + this.duration = duration; |
| + }, |
| + |
| + /** |
| + * @param {!WebInspector.TracingAgent.Event} payload |
| + */ |
| + _complete: function(payload) |
| + { |
| + if (this.name !== payload.name) { |
| + console.assert(false, "Open/close event mismatch: " + JSON.stringify(this._payload) + " vs. " + JSON.stringify(payload)); |
|
pfeldman
2014/03/27 13:09:53
It is fine to complete the event you did not start
caseq
2014/03/28 17:13:41
This check shouldn't normally hit in the case you
|
| + return; |
| + } |
| + var duration = payload.ts - this.startTime; |
| + if (duration < 0) { |
| + console.assert(false, "Event out of order: " + this.name); |
| + return; |
| + } |
| + this._setDuration(duration); |
| + } |
| +}; |
| + |
| +/** |
| + * @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 = {}; |
| +} |
| + |
| +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(id); |
| + this._threads[id] = thread; |
| + } |
| + return thread; |
| + }, |
| + |
| + /** |
| + * @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 {number} id |
| + */ |
| +WebInspector.TracingModel.Thread = function(id) |
| +{ |
| + WebInspector.TracingModel.NamedObject.call(this); |
| + this._setName("Thread " + id); |
| + this._events = []; |
| + this._stack = []; |
| + this._maxStackDepth = 0; |
| +} |
| + |
| +WebInspector.TracingModel.Thread.prototype = { |
| + /** |
| + * @param {!WebInspector.TracingAgent.Event} payload |
| + */ |
| + addEvent: function(payload) |
| + { |
| + for (var top = this._stack.peekLast(); top && top.endTime && top.endTime <= payload.ts;) { |
| + this._stack.pop(); |
| + top = this._stack.peekLast(); |
| + } |
| + if (payload.ph === WebInspector.TracingAgent.Phase.End) { |
| + var openEvent = this._stack.pop(); |
| + // Quietly ignore unbalanced close events, they're legit (we could have missed start one). |
| + if (openEvent) |
| + openEvent._complete(payload); |
| + return; |
| + } |
| + |
| + var event = new WebInspector.TracingModel.Event(payload, this._stack.length); |
| + if (payload.ph === WebInspector.TracingAgent.Phase.Begin || payload.ph === WebInspector.TracingAgent.Phase.Complete) { |
| + if (payload.ph === WebInspector.TracingAgent.Phase.Complete) |
| + event._setDuration(payload.dur); |
| + this._stack.push(event); |
|
pfeldman
2014/03/27 13:09:53
You should not put the complete event into the sta
caseq
2014/03/28 17:13:41
I should -- we wouldn't compute level properly oth
|
| + 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 {!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 |
| +} |