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