Home | History | Annotate | Download | only in timeline
      1 /*
      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.
      5  */
      6 
      7 /**
      8  * @constructor
      9  */
     10 WebInspector.TracingModel = function()
     11 {
     12     this.reset();
     13 }
     14 
     15 /**
     16  * @enum {string}
     17  */
     18 WebInspector.TracingModel.Phase = {
     19     Begin: "B",
     20     End: "E",
     21     Complete: "X",
     22     Instant: "I",
     23     AsyncBegin: "S",
     24     AsyncStepInto: "T",
     25     AsyncStepPast: "p",
     26     AsyncEnd: "F",
     27     FlowBegin: "s",
     28     FlowStep: "t",
     29     FlowEnd: "f",
     30     Metadata: "M",
     31     Counter: "C",
     32     Sample: "P",
     33     CreateObject: "N",
     34     SnapshotObject: "O",
     35     DeleteObject: "D"
     36 };
     37 
     38 WebInspector.TracingModel.MetadataEvent = {
     39     ProcessSortIndex: "process_sort_index",
     40     ProcessName: "process_name",
     41     ThreadSortIndex: "thread_sort_index",
     42     ThreadName: "thread_name"
     43 }
     44 
     45 WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline";
     46 
     47 WebInspector.TracingModel.ConsoleEventCategory = "blink.console";
     48 
     49 WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools";
     50 
     51 WebInspector.TracingModel.DevToolsMetadataEvent = {
     52     TracingStartedInPage: "TracingStartedInPage",
     53     TracingSessionIdForWorker: "TracingSessionIdForWorker",
     54 };
     55 
     56 /**
     57  * @param {string} phase
     58  * @return {boolean}
     59  */
     60 WebInspector.TracingModel.isAsyncPhase = function(phase)
     61 {
     62     return phase === WebInspector.TracingModel.Phase.AsyncBegin || phase === WebInspector.TracingModel.Phase.AsyncEnd ||
     63         phase === WebInspector.TracingModel.Phase.AsyncStepInto || phase === WebInspector.TracingModel.Phase.AsyncStepPast;
     64 }
     65 
     66 WebInspector.TracingModel.prototype = {
     67     /**
     68      * @return {!Array.<!WebInspector.TracingModel.Event>}
     69      */
     70     devtoolsPageMetadataEvents: function()
     71     {
     72         return this._devtoolsPageMetadataEvents;
     73     },
     74 
     75     /**
     76      * @return {!Array.<!WebInspector.TracingModel.Event>}
     77      */
     78     devtoolsWorkerMetadataEvents: function()
     79     {
     80         return this._devtoolsWorkerMetadataEvents;
     81     },
     82 
     83     /**
     84      * @return {?string}
     85      */
     86     sessionId: function()
     87     {
     88         return this._sessionId;
     89     },
     90 
     91     /**
     92      * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
     93      */
     94     setEventsForTest: function(events)
     95     {
     96         this.reset();
     97         this.addEvents(events);
     98         this.tracingComplete();
     99     },
    100 
    101     /**
    102      * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
    103      */
    104     addEvents: function(events)
    105     {
    106         for (var i = 0; i < events.length; ++i)
    107             this._addEvent(events[i]);
    108     },
    109 
    110     tracingComplete: function()
    111     {
    112         this._processMetadataEvents();
    113         for (var process in this._processById)
    114             this._processById[process]._tracingComplete(this._maximumRecordTime);
    115         this._backingStorage.finishWriting(function() {});
    116     },
    117 
    118     reset: function()
    119     {
    120         this._processById = {};
    121         this._minimumRecordTime = 0;
    122         this._maximumRecordTime = 0;
    123         this._sessionId = null;
    124         this._devtoolsPageMetadataEvents = [];
    125         this._devtoolsWorkerMetadataEvents = [];
    126         if (this._backingStorage)
    127             this._backingStorage.remove();
    128         this._backingStorage = new WebInspector.DeferredTempFile("tracing", String(Date.now()));
    129         this._storageOffset = 0;
    130     },
    131 
    132     /**
    133      * @param {!WebInspector.OutputStream} outputStream
    134      * @param {!WebInspector.OutputStreamDelegate} delegate
    135      */
    136     writeToStream: function(outputStream, delegate)
    137     {
    138         this._backingStorage.writeToOutputStream(outputStream, delegate);
    139     },
    140 
    141     /**
    142       * @param {!WebInspector.TracingManager.EventPayload} payload
    143       */
    144     _addEvent: function(payload)
    145     {
    146         var process = this._processById[payload.pid];
    147         if (!process) {
    148             process = new WebInspector.TracingModel.Process(payload.pid);
    149             this._processById[payload.pid] = process;
    150         }
    151 
    152         var stringPayload = JSON.stringify(payload);
    153         var startOffset = this._storageOffset;
    154         if (startOffset) {
    155             var recordDelimiter = ",\n";
    156             stringPayload = recordDelimiter + stringPayload;
    157             startOffset += recordDelimiter.length;
    158         }
    159         this._storageOffset += stringPayload.length;
    160         this._backingStorage.write([stringPayload]);
    161 
    162         if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) {
    163             var timestamp = payload.ts / 1000;
    164             // We do allow records for unrelated threads to arrive out-of-order,
    165             // so there's a chance we're getting records from the past.
    166             if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime))
    167                 this._minimumRecordTime = timestamp;
    168             var endTimeStamp = (payload.ts + (payload.dur || 0)) / 1000;
    169             this._maximumRecordTime = Math.max(this._maximumRecordTime, endTimeStamp);
    170             var event = process._addEvent(payload);
    171             if (!event)
    172                 return;
    173             event._setBackingStorage(this._backingStorage, startOffset, this._storageOffset);
    174             if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage &&
    175                 event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory) {
    176                 this._devtoolsPageMetadataEvents.push(event);
    177             }
    178             if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingSessionIdForWorker &&
    179                 event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory) {
    180                 this._devtoolsWorkerMetadataEvents.push(event);
    181             }
    182             return;
    183         }
    184         switch (payload.name) {
    185         case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex:
    186             process._setSortIndex(payload.args["sort_index"]);
    187             break;
    188         case WebInspector.TracingModel.MetadataEvent.ProcessName:
    189             process._setName(payload.args["name"]);
    190             break;
    191         case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex:
    192             process.threadById(payload.tid)._setSortIndex(payload.args["sort_index"]);
    193             break;
    194         case WebInspector.TracingModel.MetadataEvent.ThreadName:
    195             process.threadById(payload.tid)._setName(payload.args["name"]);
    196             break;
    197         }
    198     },
    199 
    200     _processMetadataEvents: function()
    201     {
    202         this._devtoolsPageMetadataEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
    203         if (!this._devtoolsPageMetadataEvents.length) {
    204             WebInspector.console.error(WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage + " event not found.");
    205             return;
    206         }
    207         var sessionId = this._devtoolsPageMetadataEvents[0].args["sessionId"];
    208         this._sessionId = sessionId;
    209 
    210         var mismatchingIds = {};
    211         function checkSessionId(event)
    212         {
    213             var args = event.args;
    214             // FIXME: put sessionId into args["data"] for TracingStartedInPage event.
    215             if (args["data"])
    216                 args = args["data"];
    217             var id = args["sessionId"];
    218             if (id === sessionId)
    219                 return true;
    220             mismatchingIds[id] = true;
    221             return false;
    222         }
    223         this._devtoolsPageMetadataEvents = this._devtoolsPageMetadataEvents.filter(checkSessionId);
    224         this._devtoolsWorkerMetadataEvents = this._devtoolsWorkerMetadataEvents.filter(checkSessionId);
    225 
    226         var idList = Object.keys(mismatchingIds);
    227         if (idList.length)
    228             WebInspector.console.error("Timeline recording was started in more than one page simulaniously. Session id mismatch: " + this._sessionId + " and " + idList + ".");
    229     },
    230 
    231     /**
    232      * @return {number}
    233      */
    234     minimumRecordTime: function()
    235     {
    236         return this._minimumRecordTime;
    237     },
    238 
    239     /**
    240      * @return {number}
    241      */
    242     maximumRecordTime: function()
    243     {
    244         return this._maximumRecordTime;
    245     },
    246 
    247     /**
    248      * @return {!Array.<!WebInspector.TracingModel.Process>}
    249      */
    250     sortedProcesses: function()
    251     {
    252         return WebInspector.TracingModel.NamedObject._sort(Object.values(this._processById));
    253     }
    254 }
    255 
    256 
    257 /**
    258  * @constructor
    259  * @param {!WebInspector.TracingModel} tracingModel
    260  */
    261 WebInspector.TracingModel.Loader = function(tracingModel)
    262 {
    263     this._tracingModel = tracingModel;
    264     this._firstChunkReceived = false;
    265 }
    266 
    267 WebInspector.TracingModel.Loader.prototype = {
    268     /**
    269      * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
    270      */
    271     loadNextChunk: function(events)
    272     {
    273         if (!this._firstChunkReceived) {
    274             this._tracingModel.reset();
    275             this._firstChunkReceived = true;
    276         }
    277         this._tracingModel.addEvents(events);
    278     },
    279 
    280     finish: function()
    281     {
    282         this._tracingModel.tracingComplete();
    283     }
    284 }
    285 
    286 
    287 /**
    288  * @constructor
    289  * @param {string} category
    290  * @param {string} name
    291  * @param {string} phase
    292  * @param {number} startTime
    293  * @param {?WebInspector.TracingModel.Thread} thread
    294  */
    295 WebInspector.TracingModel.Event = function(category, name, phase, startTime, thread)
    296 {
    297     this.category = category;
    298     this.name = name;
    299     this.phase = phase;
    300     this.startTime = startTime;
    301     this.thread = thread;
    302     this.args = {};
    303 
    304     /** @type {?string} */
    305     this.warning = null;
    306     /** @type {?WebInspector.TracingModel.Event} */
    307     this.initiator = null;
    308     /** @type {?Array.<!ConsoleAgent.CallFrame>} */
    309     this.stackTrace = null;
    310     /** @type {?Element} */
    311     this.previewElement = null;
    312     /** @type {?string} */
    313     this.imageURL = null;
    314     /** @type {number} */
    315     this.backendNodeId = 0;
    316 
    317     /** @type {number} */
    318     this.selfTime = 0;
    319 }
    320 
    321 /**
    322  * @param {!WebInspector.TracingManager.EventPayload} payload
    323  * @param {?WebInspector.TracingModel.Thread} thread
    324  * @return {!WebInspector.TracingModel.Event}
    325  */
    326 WebInspector.TracingModel.Event.fromPayload = function(payload, thread)
    327 {
    328     var event = new WebInspector.TracingModel.Event(payload.cat, payload.name, payload.ph, payload.ts / 1000, thread);
    329     if (payload.args)
    330         event.addArgs(payload.args);
    331     else
    332         console.error("Missing mandatory event argument 'args' at " + payload.ts / 1000);
    333     if (typeof payload.dur === "number")
    334         event.setEndTime((payload.ts + payload.dur) / 1000);
    335     if (payload.id)
    336         event.id = payload.id;
    337     return event;
    338 }
    339 
    340 WebInspector.TracingModel.Event.prototype = {
    341     /**
    342      * @param {number} endTime
    343      */
    344     setEndTime: function(endTime)
    345     {
    346         if (endTime < this.startTime) {
    347             console.assert(false, "Event out of order: " + this.name);
    348             return;
    349         }
    350         this.endTime = endTime;
    351         this.duration = endTime - this.startTime;
    352     },
    353 
    354     /**
    355      * @param {!Object} args
    356      */
    357     addArgs: function(args)
    358     {
    359         // Shallow copy args to avoid modifying original payload which may be saved to file.
    360         for (var name in args) {
    361             if (name in this.args)
    362                 console.error("Same argument name (" + name +  ") is used for begin and end phases of " + this.name);
    363             this.args[name] = args[name];
    364         }
    365     },
    366 
    367     /**
    368      * @param {!WebInspector.TracingManager.EventPayload} payload
    369      */
    370     _complete: function(payload)
    371     {
    372         if (payload.args)
    373             this.addArgs(payload.args);
    374         else
    375             console.error("Missing mandatory event argument 'args' at " + payload.ts / 1000);
    376         this.setEndTime(payload.ts / 1000);
    377     },
    378 
    379     /**
    380      * @param {!WebInspector.DeferredTempFile} backingFile
    381      * @param {number} startOffset
    382      * @param {number} endOffset
    383      */
    384     _setBackingStorage: function(backingFile, startOffset, endOffset)
    385     {
    386     }
    387 }
    388 
    389 /**
    390  * @param {!WebInspector.TracingModel.Event} a
    391  * @param {!WebInspector.TracingModel.Event} b
    392  * @return {number}
    393  */
    394 WebInspector.TracingModel.Event.compareStartTime = function (a, b)
    395 {
    396     return a.startTime - b.startTime;
    397 }
    398 
    399 /**
    400  * @param {!WebInspector.TracingModel.Event} a
    401  * @param {!WebInspector.TracingModel.Event} b
    402  * @return {number}
    403  */
    404 WebInspector.TracingModel.Event.orderedCompareStartTime = function (a, b)
    405 {
    406     // Array.mergeOrdered coalesces objects if comparator returns 0.
    407     // To change this behavior this comparator return -1 in the case events
    408     // startTime's are equal, so both events got placed into the result array.
    409     return a.startTime - b.startTime || -1;
    410 }
    411 
    412 /**
    413  * @constructor
    414  * @extends {WebInspector.TracingModel.Event}
    415  * @param {string} category
    416  * @param {string} name
    417  * @param {number} startTime
    418  * @param {?WebInspector.TracingModel.Thread} thread
    419  */
    420 WebInspector.TracingModel.ObjectSnapshot = function(category, name, startTime, thread)
    421 {
    422     WebInspector.TracingModel.Event.call(this, category, name, WebInspector.TracingModel.Phase.SnapshotObject, startTime, thread);
    423 }
    424 
    425 /**
    426  * @param {!WebInspector.TracingManager.EventPayload} payload
    427  * @param {?WebInspector.TracingModel.Thread} thread
    428  * @return {!WebInspector.TracingModel.ObjectSnapshot}
    429  */
    430 WebInspector.TracingModel.ObjectSnapshot.fromPayload = function(payload, thread)
    431 {
    432     var snapshot = new WebInspector.TracingModel.ObjectSnapshot(payload.cat, payload.name, payload.ts / 1000, thread);
    433     if (payload.id)
    434         snapshot.id = payload.id;
    435     if (!payload.args || !payload.args["snapshot"]) {
    436         console.error("Missing mandatory 'snapshot' argument at " + payload.ts / 1000);
    437         return snapshot;
    438     }
    439     if (payload.args)
    440         snapshot.addArgs(payload.args);
    441     return snapshot;
    442 }
    443 
    444 WebInspector.TracingModel.ObjectSnapshot.prototype = {
    445    /**
    446     * @param {function(?Object)} callback
    447     */
    448    requestObject: function(callback)
    449    {
    450        var snapshot = this.args["snapshot"];
    451        if (snapshot) {
    452            callback(snapshot);
    453            return;
    454        }
    455        this._file.readRange(this._startOffset, this._endOffset, onRead);
    456        /**
    457         * @param {?string} result
    458         */
    459        function onRead(result)
    460        {
    461            if (!result) {
    462                callback(null);
    463                return;
    464            }
    465            var snapshot;
    466            try {
    467                var payload = JSON.parse(result);
    468                snapshot = payload["args"]["snapshot"];
    469            } catch (e) {
    470                WebInspector.console.error("Malformed event data in backing storage");
    471            }
    472            callback(snapshot);
    473        }
    474     },
    475 
    476     /**
    477      * @param {!WebInspector.DeferredTempFile} backingFile
    478      * @param {number} startOffset
    479      * @param {number} endOffset
    480      * @override
    481      */
    482     _setBackingStorage: function(backingFile, startOffset, endOffset)
    483     {
    484         if (endOffset - startOffset < 10000)
    485             return;
    486         this._file = backingFile;
    487         this._startOffset = startOffset;
    488         this._endOffset = endOffset;
    489         this.args = {};
    490     },
    491 
    492     __proto__: WebInspector.TracingModel.Event.prototype
    493 }
    494 
    495 
    496 /**
    497  * @constructor
    498  */
    499 WebInspector.TracingModel.NamedObject = function()
    500 {
    501 }
    502 
    503 WebInspector.TracingModel.NamedObject.prototype =
    504 {
    505     /**
    506      * @param {string} name
    507      */
    508     _setName: function(name)
    509     {
    510         this._name = name;
    511     },
    512 
    513     /**
    514      * @return {string}
    515      */
    516     name: function()
    517     {
    518         return this._name;
    519     },
    520 
    521     /**
    522      * @param {number} sortIndex
    523      */
    524     _setSortIndex: function(sortIndex)
    525     {
    526         this._sortIndex = sortIndex;
    527     },
    528 }
    529 
    530 /**
    531  * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array
    532  */
    533 WebInspector.TracingModel.NamedObject._sort = function(array)
    534 {
    535     /**
    536      * @param {!WebInspector.TracingModel.NamedObject} a
    537      * @param {!WebInspector.TracingModel.NamedObject} b
    538      */
    539     function comparator(a, b)
    540     {
    541         return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.name().localeCompare(b.name());
    542     }
    543     return array.sort(comparator);
    544 }
    545 
    546 /**
    547  * @constructor
    548  * @extends {WebInspector.TracingModel.NamedObject}
    549  * @param {number} id
    550  */
    551 WebInspector.TracingModel.Process = function(id)
    552 {
    553     WebInspector.TracingModel.NamedObject.call(this);
    554     this._setName("Process " + id);
    555     this._threads = {};
    556     this._objects = {};
    557     /** @type {!Array.<!WebInspector.TracingManager.EventPayload>} */
    558     this._asyncEvents = [];
    559     /** @type {!Object.<string, ?Array.<!WebInspector.TracingModel.Event>>} */
    560     this._openAsyncEvents = [];
    561 }
    562 
    563 WebInspector.TracingModel.Process.prototype = {
    564     /**
    565      * @param {number} id
    566      * @return {!WebInspector.TracingModel.Thread}
    567      */
    568     threadById: function(id)
    569     {
    570         var thread = this._threads[id];
    571         if (!thread) {
    572             thread = new WebInspector.TracingModel.Thread(this, id);
    573             this._threads[id] = thread;
    574         }
    575         return thread;
    576     },
    577 
    578     /**
    579      * @param {!WebInspector.TracingManager.EventPayload} payload
    580      * @return {?WebInspector.TracingModel.Event} event
    581      */
    582     _addEvent: function(payload)
    583     {
    584         var phase = WebInspector.TracingModel.Phase;
    585         // Build async event when we've got events from all threads, so we can sort them and process in the chronological order.
    586         // However, also add individual async events to the thread flow, so we can easily display them on the same chart as
    587         // other events, should we choose so.
    588         if (WebInspector.TracingModel.isAsyncPhase(payload.ph))
    589             this._asyncEvents.push(payload);
    590 
    591         var event = this.threadById(payload.tid)._addEvent(payload);
    592         if (event && payload.ph === phase.SnapshotObject)
    593             this.objectsByName(event.name).push(event);
    594         return event;
    595     },
    596 
    597     /**
    598      * @param {!number} lastEventTime
    599      */
    600     _tracingComplete: function(lastEventTime)
    601     {
    602         /**
    603          * @param {!WebInspector.TracingManager.EventPayload} a
    604          * @param {!WebInspector.TracingManager.EventPayload} b
    605          */
    606         function comparePayloadTimestamp(a, b)
    607         {
    608             return a.ts - b.ts;
    609         }
    610         this._asyncEvents.sort(comparePayloadTimestamp).forEach(this._addAsyncEvent, this);
    611         for (var key in this._openAsyncEvents) {
    612             var steps = this._openAsyncEvents[key];
    613             if (!steps)
    614                 continue;
    615             var startEvent = steps[0];
    616             var syntheticEndEvent = new WebInspector.TracingModel.Event(startEvent.category, startEvent.name, WebInspector.TracingModel.Phase.AsyncEnd, lastEventTime, startEvent.thread);
    617             steps.push(syntheticEndEvent);
    618         }
    619         this._asyncEvents = [];
    620         this._openAsyncEvents = [];
    621     },
    622 
    623     /**
    624      * @param {!WebInspector.TracingManager.EventPayload} payload
    625      */
    626     _addAsyncEvent: function(payload)
    627     {
    628         var phase = WebInspector.TracingModel.Phase;
    629         var timestamp = payload.ts / 1000;
    630         var key = payload.name + "." + payload.id;
    631         var steps = this._openAsyncEvents[key];
    632 
    633         var thread = this.threadById(payload.tid);
    634         if (payload.ph === phase.AsyncBegin) {
    635             if (steps) {
    636                 console.error("Event " + payload.name + " has already been started");
    637                 return;
    638             }
    639             steps = [WebInspector.TracingModel.Event.fromPayload(payload, thread)];
    640             this._openAsyncEvents[key] = steps;
    641             thread._addAsyncEventSteps(steps);
    642             return;
    643         }
    644         if (!steps) {
    645             console.error("Unexpected async event " + payload.name + ", phase " + payload.ph);
    646             return;
    647         }
    648         var newEvent = WebInspector.TracingModel.Event.fromPayload(payload, thread);
    649         if (payload.ph === phase.AsyncEnd) {
    650             steps.push(newEvent);
    651             delete this._openAsyncEvents[key];
    652         } else if (payload.ph === phase.AsyncStepInto || payload.ph === phase.AsyncStepPast) {
    653             var lastPhase = steps.peekLast().phase;
    654             if (lastPhase !== phase.AsyncBegin && lastPhase !== payload.ph) {
    655                 console.assert(false, "Async event step phase mismatch: " + lastPhase + " at " + steps.peekLast().startTime + " vs. " + payload.ph + " at " + timestamp);
    656                 return;
    657             }
    658             steps.push(newEvent);
    659         } else {
    660             console.assert(false, "Invalid async event phase");
    661         }
    662     },
    663 
    664     /**
    665      * @param {string} name
    666      * @return {!Array.<!WebInspector.TracingModel.Event>}
    667      */
    668     objectsByName: function(name)
    669     {
    670         var objects = this._objects[name];
    671         if (!objects) {
    672             objects = [];
    673             this._objects[name] = objects;
    674         }
    675         return objects;
    676     },
    677 
    678     /**
    679      * @return {!Array.<string>}
    680      */
    681     sortedObjectNames: function()
    682     {
    683         return Object.keys(this._objects).sort();
    684     },
    685 
    686     /**
    687      * @return {!Array.<!WebInspector.TracingModel.Thread>}
    688      */
    689     sortedThreads: function()
    690     {
    691         return WebInspector.TracingModel.NamedObject._sort(Object.values(this._threads));
    692     },
    693 
    694     __proto__: WebInspector.TracingModel.NamedObject.prototype
    695 }
    696 
    697 /**
    698  * @constructor
    699  * @extends {WebInspector.TracingModel.NamedObject}
    700  * @param {!WebInspector.TracingModel.Process} process
    701  * @param {number} id
    702  */
    703 WebInspector.TracingModel.Thread = function(process, id)
    704 {
    705     WebInspector.TracingModel.NamedObject.call(this);
    706     this._process = process;
    707     this._setName("Thread " + id);
    708     this._events = [];
    709     this._asyncEvents = [];
    710     this._id = id;
    711 
    712     this._stack = [];
    713 }
    714 
    715 WebInspector.TracingModel.Thread.prototype = {
    716 
    717     /**
    718      * @return {?WebInspector.Target}
    719      */
    720     target: function()
    721     {
    722         //FIXME: correctly specify target
    723         return WebInspector.targetManager.targets()[0];
    724     },
    725 
    726     /**
    727      * @param {!WebInspector.TracingManager.EventPayload} payload
    728      * @return {?WebInspector.TracingModel.Event} event
    729      */
    730     _addEvent: function(payload)
    731     {
    732         var timestamp = payload.ts / 1000;
    733         if (payload.ph === WebInspector.TracingModel.Phase.End) {
    734             // Quietly ignore unbalanced close events, they're legit (we could have missed start one).
    735             if (!this._stack.length)
    736                 return null;
    737             var top = this._stack.pop();
    738             if (top.name !== payload.name || top.category !== payload.cat)
    739                 console.error("B/E events mismatch at " + top.startTime + " (" + top.name + ") vs. " + timestamp + " (" + payload.name + ")");
    740             else
    741                 top._complete(payload);
    742             return null;
    743         }
    744         var event = payload.ph === WebInspector.TracingModel.Phase.SnapshotObject
    745             ? WebInspector.TracingModel.ObjectSnapshot.fromPayload(payload, this)
    746             : WebInspector.TracingModel.Event.fromPayload(payload, this);
    747         if (payload.ph === WebInspector.TracingModel.Phase.Begin)
    748             this._stack.push(event);
    749         if (this._events.length && this._events.peekLast().startTime > event.startTime)
    750             console.assert(false, "Event is out of order: " + event.name);
    751         this._events.push(event);
    752         return event;
    753     },
    754 
    755     /**
    756      * @param {!Array.<!WebInspector.TracingModel.Event>} eventSteps
    757      */
    758     _addAsyncEventSteps: function(eventSteps)
    759     {
    760         this._asyncEvents.push(eventSteps);
    761     },
    762 
    763     /**
    764      * @return {number}
    765      */
    766     id: function()
    767     {
    768         return this._id;
    769     },
    770 
    771     /**
    772      * @return {!WebInspector.TracingModel.Process}
    773      */
    774     process: function()
    775     {
    776         return this._process;
    777     },
    778 
    779     /**
    780      * @return {!Array.<!WebInspector.TracingModel.Event>}
    781      */
    782     events: function()
    783     {
    784         return this._events;
    785     },
    786 
    787     /**
    788      * @return {!Array.<!WebInspector.TracingModel.Event>}
    789      */
    790     asyncEvents: function()
    791     {
    792         return this._asyncEvents;
    793     },
    794 
    795     __proto__: WebInspector.TracingModel.NamedObject.prototype
    796 }
    797