Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / timeline / TracingModel.js
index c327bf8..4877ded 100644 (file)
@@ -6,35 +6,12 @@
 
 /**
  * @constructor
- * @extends {WebInspector.TargetAwareObject}
  */
-WebInspector.TracingModel = function(target)
+WebInspector.TracingModel = function()
 {
-    WebInspector.TargetAwareObject.call(this, target);
     this.reset();
-    this._active = false;
-    InspectorBackend.registerTracingDispatcher(new WebInspector.TracingDispatcher(this));
 }
 
-WebInspector.TracingModel.Events = {
-    "BufferUsage": "BufferUsage"
-}
-
-/** @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}
  */
@@ -42,11 +19,14 @@ WebInspector.TracingModel.Phase = {
     Begin: "B",
     End: "E",
     Complete: "X",
-    Instant: "i",
+    Instant: "I",
     AsyncBegin: "S",
     AsyncStepInto: "T",
     AsyncStepPast: "p",
     AsyncEnd: "F",
+    NestableAsyncBegin: "b",
+    NestableAsyncEnd: "e",
+    NestableAsyncInstant: "i",
     FlowBegin: "s",
     FlowStep: "t",
     FlowEnd: "f",
@@ -67,56 +47,70 @@ WebInspector.TracingModel.MetadataEvent = {
 
 WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline";
 
+WebInspector.TracingModel.ConsoleEventCategory = "blink.console";
+
 WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools";
 
 WebInspector.TracingModel.DevToolsMetadataEvent = {
     TracingStartedInPage: "TracingStartedInPage",
+    TracingSessionIdForWorker: "TracingSessionIdForWorker",
 };
 
+WebInspector.TracingModel._nestableAsyncEventsString =
+    WebInspector.TracingModel.Phase.NestableAsyncBegin +
+    WebInspector.TracingModel.Phase.NestableAsyncEnd +
+    WebInspector.TracingModel.Phase.NestableAsyncInstant;
+
+WebInspector.TracingModel._legacyAsyncEventsString =
+    WebInspector.TracingModel.Phase.AsyncBegin +
+    WebInspector.TracingModel.Phase.AsyncEnd +
+    WebInspector.TracingModel.Phase.AsyncStepInto +
+    WebInspector.TracingModel.Phase.AsyncStepPast;
+
+WebInspector.TracingModel._asyncEventsString = WebInspector.TracingModel._nestableAsyncEventsString + WebInspector.TracingModel._legacyAsyncEventsString;
+
+/**
+ * @param {string} phase
+ * @return {boolean}
+ */
+WebInspector.TracingModel.isNestableAsyncPhase = function(phase)
+{
+    return WebInspector.TracingModel._nestableAsyncEventsString.indexOf(phase) >= 0;
+}
+
+/**
+ * @param {string} phase
+ * @return {boolean}
+ */
+WebInspector.TracingModel.isAsyncBeginPhase = function(phase)
+{
+    return phase === WebInspector.TracingModel.Phase.AsyncBegin || phase === WebInspector.TracingModel.Phase.NestableAsyncBegin;
+}
+
+/**
+ * @param {string} phase
+ * @return {boolean}
+ */
+WebInspector.TracingModel.isAsyncPhase = function(phase)
+{
+    return WebInspector.TracingModel._asyncEventsString.indexOf(phase) >= 0;
+}
+
 WebInspector.TracingModel.prototype = {
     /**
      * @return {!Array.<!WebInspector.TracingModel.Event>}
      */
-    devtoolsMetadataEvents: function()
+    devtoolsPageMetadataEvents: function()
     {
-        return this._devtoolsMetadataEvents;
+        return this._devtoolsPageMetadataEvents;
     },
 
     /**
-     * @param {string} categoryFilter
-     * @param {string} options
-     * @param {function(?string)=} callback
-     */
-    start: function(categoryFilter, options, callback)
-    {
-        this.reset();
-        var bufferUsageReportingIntervalMs = 500;
-        /**
-         * @param {?string} error
-         * @param {string} sessionId
-         * @this {WebInspector.TracingModel}
-         */
-        function callbackWrapper(error, sessionId)
-        {
-            this._sessionId = sessionId;
-            if (callback)
-                callback(error);
-        }
-        TracingAgent.start(categoryFilter, options, bufferUsageReportingIntervalMs, callbackWrapper.bind(this));
-        this._active = true;
-    },
-
-    /**
-     * @param {function()} callback
+     * @return {!Array.<!WebInspector.TracingModel.Event>}
      */
-    stop: function(callback)
+    devtoolsWorkerMetadataEvents: function()
     {
-        if (!this._active) {
-            callback();
-            return;
-        }
-        this._pendingStopCallback = callback;
-        TracingAgent.end();
+        return this._devtoolsWorkerMetadataEvents;
     },
 
     /**
@@ -128,41 +122,30 @@ WebInspector.TracingModel.prototype = {
     },
 
     /**
-     * @param {string} sessionId
-     * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
+     * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
      */
-    setEventsForTest: function(sessionId, events)
+    setEventsForTest: function(events)
     {
         this.reset();
-        this._sessionId = sessionId;
-        this._eventsCollected(events);
-        this._tracingComplete();
+        this.addEvents(events);
+        this.tracingComplete();
     },
 
     /**
-     * @param {number} usage
+     * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
      */
-    _bufferUsage: function(usage)
-    {
-        this.dispatchEventToListeners(WebInspector.TracingModel.Events.BufferUsage, usage);
-    },
-
-    /**
-     * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
-     */
-    _eventsCollected: function(events)
+    addEvents: function(events)
     {
         for (var i = 0; i < events.length; ++i)
             this._addEvent(events[i]);
     },
 
-    _tracingComplete: function()
+    tracingComplete: function()
     {
-        this._active = false;
-        if (!this._pendingStopCallback)
-            return;
-        this._pendingStopCallback();
-        this._pendingStopCallback = null;
+        this._processMetadataEvents();
+        for (var process in this._processById)
+            this._processById[process]._tracingComplete(this._maximumRecordTime);
+        this._backingStorage.finishWriting(function() {});
     },
 
     reset: function()
@@ -171,11 +154,25 @@ WebInspector.TracingModel.prototype = {
         this._minimumRecordTime = 0;
         this._maximumRecordTime = 0;
         this._sessionId = null;
-        this._devtoolsMetadataEvents = [];
+        this._devtoolsPageMetadataEvents = [];
+        this._devtoolsWorkerMetadataEvents = [];
+        if (this._backingStorage)
+            this._backingStorage.remove();
+        this._backingStorage = new WebInspector.DeferredTempFile("tracing", String(Date.now()));
+        this._storageOffset = 0;
     },
 
     /**
-      * @param {!WebInspector.TracingModel.EventPayload} payload
+     * @param {!WebInspector.OutputStream} outputStream
+     * @param {!WebInspector.OutputStreamDelegate} delegate
+     */
+    writeToStream: function(outputStream, delegate)
+    {
+        this._backingStorage.writeToOutputStream(outputStream, delegate);
+    },
+
+    /**
+      * @param {!WebInspector.TracingManager.EventPayload} payload
       */
     _addEvent: function(payload)
     {
@@ -184,22 +181,38 @@ WebInspector.TracingModel.prototype = {
             process = new WebInspector.TracingModel.Process(payload.pid);
             this._processById[payload.pid] = process;
         }
-        var thread = process.threadById(payload.tid);
+
+        var stringPayload = JSON.stringify(payload);
+        var startOffset = this._storageOffset;
+        if (startOffset) {
+            var recordDelimiter = ",\n";
+            stringPayload = recordDelimiter + stringPayload;
+            startOffset += recordDelimiter.length;
+        }
+        var blob = new Blob([stringPayload]);
+        this._storageOffset += blob.size;
+        this._backingStorage.write([stringPayload]);
+
         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._devtoolsMetadataEvents.push(event);
+            var endTimeStamp = (payload.ts + (payload.dur || 0)) / 1000;
+            this._maximumRecordTime = Math.max(this._maximumRecordTime, endTimeStamp);
+            var event = process._addEvent(payload);
+            if (!event)
+                return;
+            event._setBackingStorage(this._backingStorage, startOffset, this._storageOffset);
+            if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage &&
+                event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory) {
+                this._devtoolsPageMetadataEvents.push(event);
+            }
+            if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingSessionIdForWorker &&
+                event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory) {
+                this._devtoolsWorkerMetadataEvents.push(event);
+            }
             return;
         }
         switch (payload.name) {
@@ -210,14 +223,45 @@ WebInspector.TracingModel.prototype = {
             process._setName(payload.args["name"]);
             break;
         case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex:
-            thread._setSortIndex(payload.args["sort_index"]);
+            process.threadById(payload.tid)._setSortIndex(payload.args["sort_index"]);
             break;
         case WebInspector.TracingModel.MetadataEvent.ThreadName:
-            thread._setName(payload.args["name"]);
+            process.threadById(payload.tid)._setName(payload.args["name"]);
             break;
         }
     },
 
+    _processMetadataEvents: function()
+    {
+        this._devtoolsPageMetadataEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
+        if (!this._devtoolsPageMetadataEvents.length) {
+            WebInspector.console.error(WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage + " event not found.");
+            return;
+        }
+        var sessionId = this._devtoolsPageMetadataEvents[0].args["sessionId"];
+        this._sessionId = sessionId;
+
+        var mismatchingIds = {};
+        function checkSessionId(event)
+        {
+            var args = event.args;
+            // FIXME: put sessionId into args["data"] for TracingStartedInPage event.
+            if (args["data"])
+                args = args["data"];
+            var id = args["sessionId"];
+            if (id === sessionId)
+                return true;
+            mismatchingIds[id] = true;
+            return false;
+        }
+        this._devtoolsPageMetadataEvents = this._devtoolsPageMetadataEvents.filter(checkSessionId);
+        this._devtoolsWorkerMetadataEvents = this._devtoolsWorkerMetadataEvents.filter(checkSessionId);
+
+        var idList = Object.keys(mismatchingIds);
+        if (idList.length)
+            WebInspector.console.error("Timeline recording was started in more than one page simulaniously. Session id mismatch: " + this._sessionId + " and " + idList + ".");
+    },
+
     /**
      * @return {number}
      */
@@ -240,33 +284,61 @@ WebInspector.TracingModel.prototype = {
     sortedProcesses: function()
     {
         return WebInspector.TracingModel.NamedObject._sort(Object.values(this._processById));
-    },
-
-    __proto__: WebInspector.TargetAwareObject.prototype
+    }
 }
 
+
 /**
  * @constructor
- * @param {!WebInspector.TracingModel.EventPayload} payload
- * @param {number} level
- * @param {?WebInspector.TracingModel.Thread} thread
+ * @param {!WebInspector.TracingModel} tracingModel
  */
-WebInspector.TracingModel.Event = function(payload, level, thread)
+WebInspector.TracingModel.Loader = function(tracingModel)
 {
-    this.name = payload.name;
-    this.category = payload.cat;
-    this.startTime = payload.ts / 1000;
-    this.args = payload.args;
-    this.phase = payload.ph;
-    this.level = level;
+    this._tracingModel = tracingModel;
+    this._firstChunkReceived = false;
+}
 
-    if (payload.dur)
-        this._setEndTime((payload.ts + payload.dur) / 1000);
+WebInspector.TracingModel.Loader.prototype = {
+    /**
+     * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
+     */
+    loadNextChunk: function(events)
+    {
+        if (!this._firstChunkReceived) {
+            this._tracingModel.reset();
+            this._firstChunkReceived = true;
+        }
+        this._tracingModel.addEvents(events);
+    },
 
-    if (payload.id)
-        this.id = payload.id;
+    finish: function()
+    {
+        this._tracingModel.tracingComplete();
+    }
+}
 
+
+/**
+ * @constructor
+ * @param {string} category
+ * @param {string} name
+ * @param {!WebInspector.TracingModel.Phase} phase
+ * @param {number} startTime
+ * @param {!WebInspector.TracingModel.Thread} thread
+ */
+WebInspector.TracingModel.Event = function(category, name, phase, startTime, thread)
+{
+    /** @type {string} */
+    this.category = category;
+    /** @type {string} */
+    this.name = name;
+    /** @type {!WebInspector.TracingModel.Phase} */
+    this.phase = phase;
+    /** @type {number} */
+    this.startTime = startTime;
+    /** @type {!WebInspector.TracingModel.Thread} */
     this.thread = thread;
+    this.args = {};
 
     /** @type {?string} */
     this.warning = null;
@@ -285,11 +357,30 @@ WebInspector.TracingModel.Event = function(payload, level, thread)
     this.selfTime = 0;
 }
 
+/**
+ * @param {!WebInspector.TracingManager.EventPayload} payload
+ * @param {!WebInspector.TracingModel.Thread} thread
+ * @return {!WebInspector.TracingModel.Event}
+ */
+WebInspector.TracingModel.Event.fromPayload = function(payload, thread)
+{
+    var event = new WebInspector.TracingModel.Event(payload.cat, payload.name, /** @type {!WebInspector.TracingModel.Phase} */ (payload.ph), payload.ts / 1000, thread);
+    if (payload.args)
+        event.addArgs(payload.args);
+    else
+        console.error("Missing mandatory event argument 'args' at " + payload.ts / 1000);
+    if (typeof payload.dur === "number")
+        event.setEndTime((payload.ts + payload.dur) / 1000);
+    if (payload.id)
+        event.id = payload.id;
+    return event;
+}
+
 WebInspector.TracingModel.Event.prototype = {
     /**
      * @param {number} endTime
      */
-    _setEndTime: function(endTime)
+    setEndTime: function(endTime)
     {
         if (endTime < this.startTime) {
             console.assert(false, "Event out of order: " + this.name);
@@ -300,22 +391,37 @@ WebInspector.TracingModel.Event.prototype = {
     },
 
     /**
-     * @param {!WebInspector.TracingModel.EventPayload} payload
+     * @param {!Object} args
      */
-    _complete: function(payload)
+    addArgs: function(args)
     {
-        if (this.name !== payload.name) {
-            console.assert(false, "Open/close event mismatch: " + this.name + " vs. " + payload.name);
-            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];
-            }
+        // Shallow copy args to avoid modifying original payload which may be saved to file.
+        for (var name in 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] = args[name];
         }
-        this._setEndTime(payload.ts / 1000);
+    },
+
+    /**
+     * @param {!WebInspector.TracingManager.EventPayload} payload
+     */
+    _complete: function(payload)
+    {
+        if (payload.args)
+            this.addArgs(payload.args);
+        else
+            console.error("Missing mandatory event argument 'args' at " + payload.ts / 1000);
+        this.setEndTime(payload.ts / 1000);
+    },
+
+    /**
+     * @param {!WebInspector.DeferredTempFile} backingFile
+     * @param {number} startOffset
+     * @param {number} endOffset
+     */
+    _setBackingStorage: function(backingFile, startOffset, endOffset)
+    {
     }
 }
 
@@ -330,6 +436,103 @@ WebInspector.TracingModel.Event.compareStartTime = function (a, b)
 }
 
 /**
+ * @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
+ * @extends {WebInspector.TracingModel.Event}
+ * @param {string} category
+ * @param {string} name
+ * @param {number} startTime
+ * @param {!WebInspector.TracingModel.Thread} thread
+ */
+WebInspector.TracingModel.ObjectSnapshot = function(category, name, startTime, thread)
+{
+    WebInspector.TracingModel.Event.call(this, category, name, WebInspector.TracingModel.Phase.SnapshotObject, startTime, thread);
+}
+
+/**
+ * @param {!WebInspector.TracingManager.EventPayload} payload
+ * @param {!WebInspector.TracingModel.Thread} thread
+ * @return {!WebInspector.TracingModel.ObjectSnapshot}
+ */
+WebInspector.TracingModel.ObjectSnapshot.fromPayload = function(payload, thread)
+{
+    var snapshot = new WebInspector.TracingModel.ObjectSnapshot(payload.cat, payload.name, payload.ts / 1000, thread);
+    if (payload.id)
+        snapshot.id = payload.id;
+    if (!payload.args || !payload.args["snapshot"]) {
+        console.error("Missing mandatory 'snapshot' argument at " + payload.ts / 1000);
+        return snapshot;
+    }
+    if (payload.args)
+        snapshot.addArgs(payload.args);
+    return snapshot;
+}
+
+WebInspector.TracingModel.ObjectSnapshot.prototype = {
+   /**
+    * @param {function(?Object)} callback
+    */
+   requestObject: function(callback)
+   {
+       var snapshot = this.args["snapshot"];
+       if (snapshot) {
+           callback(snapshot);
+           return;
+       }
+       this._file.readRange(this._startOffset, this._endOffset, onRead);
+       /**
+        * @param {?string} result
+        */
+       function onRead(result)
+       {
+           if (!result) {
+               callback(null);
+               return;
+           }
+           var snapshot;
+           try {
+               var payload = JSON.parse(result);
+               snapshot = payload["args"]["snapshot"];
+           } catch (e) {
+               WebInspector.console.error("Malformed event data in backing storage");
+           }
+           callback(snapshot);
+       }
+    },
+
+    /**
+     * @param {!WebInspector.DeferredTempFile} backingFile
+     * @param {number} startOffset
+     * @param {number} endOffset
+     * @override
+     */
+    _setBackingStorage: function(backingFile, startOffset, endOffset)
+    {
+        if (endOffset - startOffset < 10000)
+            return;
+        this._file = backingFile;
+        this._startOffset = startOffset;
+        this._endOffset = endOffset;
+        this.args = {};
+    },
+
+    __proto__: WebInspector.TracingModel.Event.prototype
+}
+
+
+/**
  * @constructor
  */
 WebInspector.TracingModel.NamedObject = function()
@@ -388,12 +591,27 @@ WebInspector.TracingModel.Process = function(id)
 {
     WebInspector.TracingModel.NamedObject.call(this);
     this._setName("Process " + id);
+    this._id = id;
     this._threads = {};
     this._objects = {};
+    /** @type {!Array.<!WebInspector.TracingModel.Event>} */
+    this._asyncEvents = [];
+    /** @type {!Object.<string, ?Array.<!WebInspector.TracingModel.Event>>} */
+    this._openAsyncEvents = {};
+    /** @type {!Object.<string, !Array.<!WebInspector.TracingModel.Event>>} */
+    this._openNestableAsyncEvents = {};
 }
 
 WebInspector.TracingModel.Process.prototype = {
     /**
+     * @return {number}
+     */
+    id: function()
+    {
+        return this._id;
+    },
+
+    /**
      * @param {number} id
      * @return {!WebInspector.TracingModel.Thread}
      */
@@ -408,11 +626,128 @@ WebInspector.TracingModel.Process.prototype = {
     },
 
     /**
+     * @param {!WebInspector.TracingManager.EventPayload} payload
+     * @return {?WebInspector.TracingModel.Event} event
+     */
+    _addEvent: function(payload)
+    {
+        var phase = WebInspector.TracingModel.Phase;
+
+        var event = this.threadById(payload.tid)._addEvent(payload);
+        if (!event)
+            return null;
+        // Build async event when we've got events from all threads, so we can sort them and process in the chronological order.
+        // However, also add individual async events to the thread flow (above), so we can easily display them on the same chart as
+        // other events, should we choose so.
+        if (WebInspector.TracingModel.isAsyncPhase(payload.ph))
+            this._asyncEvents.push(event);
+        if (payload.ph === phase.SnapshotObject)
+            this.objectsByName(event.name).push(event);
+        return event;
+    },
+
+    /**
+     * @param {!number} lastEventTimeMs
+     */
+    _tracingComplete: function(lastEventTimeMs)
+    {
+        this._asyncEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
+        for (var i = 0; i < this._asyncEvents.length; ++i) {
+            var event = this._asyncEvents[i];
+            if (WebInspector.TracingModel.isNestableAsyncPhase(event.phase))
+                this._addNestableAsyncEvent(event);
+            else
+                this._addAsyncEvent(event);
+        }
+
+        for (var key in this._openAsyncEvents) {
+            var steps = this._openAsyncEvents[key];
+            if (!steps)
+                continue;
+            var startEvent = steps[0];
+            var syntheticEndEvent = new WebInspector.TracingModel.Event(startEvent.category, startEvent.name, WebInspector.TracingModel.Phase.AsyncEnd, lastEventTimeMs, startEvent.thread);
+            steps.push(syntheticEndEvent);
+            startEvent.setEndTime(lastEventTimeMs)
+        }
+        for (var key in this._openNestableAsyncEvents) {
+            var openEvents = this._openNestableAsyncEvents[key];
+            while (openEvents.length)
+                openEvents.pop().setEndTime(lastEventTimeMs);
+        }
+        this._asyncEvents = [];
+        this._openAsyncEvents = {};
+        this._openNestableAsyncEvents = {};
+    },
+
+    /**
+     * @param {!WebInspector.TracingModel.Event} event
+     */
+    _addNestableAsyncEvent: function(event)
+    {
+        var phase = WebInspector.TracingModel.Phase;
+        var key = event.category + "." + event.id;
+        var openEventsStack = this._openNestableAsyncEvents[key];
+
+        switch (event.phase) {
+        case phase.NestableAsyncBegin:
+            if (!openEventsStack) {
+                openEventsStack = [];
+                this._openNestableAsyncEvents[key] = openEventsStack;
+            }
+            openEventsStack.push(event);
+            // fall-through intended
+        case phase.NestableAsyncInstant:
+            event.thread._addAsyncEventSteps([event]);
+            break;
+        case phase.NestableAsyncEnd:
+            if (!openEventsStack)
+                break;
+            var top = openEventsStack.pop();
+            if (top.name !== event.name) {
+                console.error("Begin/end event mismatch for nestable async event, " + top.name + " vs. " + event.name);
+                break;
+            }
+            top.setEndTime(event.startTime);
+        }
+    },
+
+    /**
      * @param {!WebInspector.TracingModel.Event} event
      */
-    addObject: function(event)
+    _addAsyncEvent: function(event)
     {
-        this.objectsByName(event.name).push(event);
+        var phase = WebInspector.TracingModel.Phase;
+        var key = event.category + "." + event.name + "." + event.id;
+        var steps = this._openAsyncEvents[key];
+
+        if (event.phase === phase.AsyncBegin) {
+            if (steps) {
+                console.error("Event " + event.name + " has already been started");
+                return;
+            }
+            steps = [event];
+            this._openAsyncEvents[key] = steps;
+            event.thread._addAsyncEventSteps(steps);
+            return;
+        }
+        if (!steps) {
+            console.error("Unexpected async event " + event.name + ", phase " + event.phase);
+            return;
+        }
+        if (event.phase === phase.AsyncEnd) {
+            steps.push(event);
+            steps[0].setEndTime(event.startTime);
+            delete this._openAsyncEvents[key];
+        } else if (event.phase === phase.AsyncStepInto || event.phase === phase.AsyncStepPast) {
+            var lastPhase = steps.peekLast().phase;
+            if (lastPhase !== phase.AsyncBegin && lastPhase !== event.phase) {
+                console.assert(false, "Async event step phase mismatch: " + lastPhase + " at " + steps.peekLast().startTime + " vs. " + event.phase + " at " + event.startTime);
+                return;
+            }
+            steps.push(event);
+        } else {
+            console.assert(false, "Invalid async event phase");
+        }
     },
 
     /**
@@ -460,99 +795,90 @@ WebInspector.TracingModel.Thread = function(process, id)
     this._process = process;
     this._setName("Thread " + id);
     this._events = [];
+    this._asyncEvents = [];
+    this._id = id;
+
     this._stack = [];
-    this._maxStackDepth = 0;
 }
 
 WebInspector.TracingModel.Thread.prototype = {
     /**
-     * @param {!WebInspector.TracingModel.EventPayload} payload
+     * @return {?WebInspector.Target}
+     */
+    target: function()
+    {
+        //FIXME: correctly specify target
+        return WebInspector.targetManager.targets()[0] || null;
+    },
+
+    /**
+     * @param {!WebInspector.TracingManager.EventPayload} payload
      * @return {?WebInspector.TracingModel.Event} event
      */
-    addEvent: function(payload)
+    _addEvent: function(payload)
     {
-        for (var top = this._stack.peekLast(); top && top.endTime && top.endTime <= payload.ts / 1000;) {
-            this._stack.pop();
-            top = this._stack.peekLast();
-        }
+        var timestamp = payload.ts / 1000;
         if (payload.ph === WebInspector.TracingModel.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);
+            if (!this._stack.length)
+                return null;
+            var top = this._stack.pop();
+            if (top.name !== payload.name || top.category !== payload.cat)
+                console.error("B/E events mismatch at " + top.startTime + " (" + top.name + ") vs. " + timestamp + " (" + payload.name + ")");
+            else
+                top._complete(payload);
             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) {
+        var event = payload.ph === WebInspector.TracingModel.Phase.SnapshotObject
+            ? WebInspector.TracingModel.ObjectSnapshot.fromPayload(payload, this)
+            : WebInspector.TracingModel.Event.fromPayload(payload, this);
+        if (payload.ph === WebInspector.TracingModel.Phase.Begin)
             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);
+            console.assert(false, "Event is out of order: " + event.name);
         this._events.push(event);
         return event;
     },
 
     /**
-     * @return {!WebInspector.TracingModel.Process}
+     * @param {!Array.<!WebInspector.TracingModel.Event>} eventSteps
      */
-    process: function()
+    _addAsyncEventSteps: function(eventSteps)
     {
-        return this._process;
+        this._asyncEvents.push(eventSteps);
     },
 
     /**
-     * @return {!Array.<!WebInspector.TracingModel.Event>}
+     * @return {number}
      */
-    events: function()
+    id: function()
     {
-        return this._events;
+        return this._id;
     },
 
     /**
-     * @return {number}
+     * @return {!WebInspector.TracingModel.Process}
      */
-    maxStackDepth: function()
+    process: function()
     {
-        // Reserve one for non-container events.
-        return this._maxStackDepth + 1;
+        return this._process;
     },
 
-    __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
+     * @return {!Array.<!WebInspector.TracingModel.Event>}
      */
-    bufferUsage: function(usage)
+    events: function()
     {
-        this._tracingModel._bufferUsage(usage);
+        return this._events;
     },
 
     /**
-     * @param {!Array.<!WebInspector.TracingModel.EventPayload>} data
+     * @return {!Array.<!WebInspector.TracingModel.Event>}
      */
-    dataCollected: function(data)
+    asyncEvents: function()
     {
-        this._tracingModel._eventsCollected(data);
+        return this._asyncEvents;
     },
 
-    tracingComplete: function()
-    {
-        this._tracingModel._tracingComplete();
-    }
+    __proto__: WebInspector.TracingModel.NamedObject.prototype
 }