• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 * @extends {WebInspector.TargetAwareObject}
10 */
11WebInspector.TracingModel = function(target)
12{
13    WebInspector.TargetAwareObject.call(this, target);
14    this.reset();
15    this._active = false;
16    InspectorBackend.registerTracingDispatcher(new WebInspector.TracingDispatcher(this));
17}
18
19WebInspector.TracingModel.Events = {
20    "BufferUsage": "BufferUsage"
21}
22
23/** @typedef {!{
24        cat: string,
25        pid: number,
26        tid: number,
27        ts: number,
28        ph: string,
29        name: string,
30        args: !Object,
31        dur: number,
32        id: number,
33        s: string
34    }}
35 */
36WebInspector.TracingModel.EventPayload;
37
38/**
39 * @enum {string}
40 */
41WebInspector.TracingModel.Phase = {
42    Begin: "B",
43    End: "E",
44    Complete: "X",
45    Instant: "i",
46    AsyncBegin: "S",
47    AsyncStepInto: "T",
48    AsyncStepPast: "p",
49    AsyncEnd: "F",
50    FlowBegin: "s",
51    FlowStep: "t",
52    FlowEnd: "f",
53    Metadata: "M",
54    Counter: "C",
55    Sample: "P",
56    CreateObject: "N",
57    SnapshotObject: "O",
58    DeleteObject: "D"
59};
60
61WebInspector.TracingModel.MetadataEvent = {
62    ProcessSortIndex: "process_sort_index",
63    ProcessName: "process_name",
64    ThreadSortIndex: "thread_sort_index",
65    ThreadName: "thread_name"
66}
67
68WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline";
69
70WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools";
71
72WebInspector.TracingModel.DevToolsMetadataEvent = {
73    TracingStartedInPage: "TracingStartedInPage",
74};
75
76WebInspector.TracingModel.prototype = {
77    /**
78     * @return {!Array.<!WebInspector.TracingModel.Event>}
79     */
80    devtoolsMetadataEvents: function()
81    {
82        return this._devtoolsMetadataEvents;
83    },
84
85    /**
86     * @param {string} categoryFilter
87     * @param {string} options
88     * @param {function(?string)=} callback
89     */
90    start: function(categoryFilter, options, callback)
91    {
92        this.reset();
93        var bufferUsageReportingIntervalMs = 500;
94        /**
95         * @param {?string} error
96         * @param {string} sessionId
97         * @this {WebInspector.TracingModel}
98         */
99        function callbackWrapper(error, sessionId)
100        {
101            this._sessionId = sessionId;
102            if (callback)
103                callback(error);
104        }
105        TracingAgent.start(categoryFilter, options, bufferUsageReportingIntervalMs, callbackWrapper.bind(this));
106        this._active = true;
107    },
108
109    /**
110     * @param {function()} callback
111     */
112    stop: function(callback)
113    {
114        if (!this._active) {
115            callback();
116            return;
117        }
118        this._pendingStopCallback = callback;
119        TracingAgent.end();
120    },
121
122    /**
123     * @return {?string}
124     */
125    sessionId: function()
126    {
127        return this._sessionId;
128    },
129
130    /**
131     * @param {string} sessionId
132     * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
133     */
134    setEventsForTest: function(sessionId, events)
135    {
136        this.reset();
137        this._sessionId = sessionId;
138        this._eventsCollected(events);
139        this._tracingComplete();
140    },
141
142    /**
143     * @param {number} usage
144     */
145    _bufferUsage: function(usage)
146    {
147        this.dispatchEventToListeners(WebInspector.TracingModel.Events.BufferUsage, usage);
148    },
149
150    /**
151     * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
152     */
153    _eventsCollected: function(events)
154    {
155        for (var i = 0; i < events.length; ++i)
156            this._addEvent(events[i]);
157    },
158
159    _tracingComplete: function()
160    {
161        this._active = false;
162        if (!this._pendingStopCallback)
163            return;
164        this._pendingStopCallback();
165        this._pendingStopCallback = null;
166    },
167
168    reset: function()
169    {
170        this._processById = {};
171        this._minimumRecordTime = 0;
172        this._maximumRecordTime = 0;
173        this._sessionId = null;
174        this._devtoolsMetadataEvents = [];
175    },
176
177    /**
178      * @param {!WebInspector.TracingModel.EventPayload} payload
179      */
180    _addEvent: function(payload)
181    {
182        var process = this._processById[payload.pid];
183        if (!process) {
184            process = new WebInspector.TracingModel.Process(payload.pid);
185            this._processById[payload.pid] = process;
186        }
187        var thread = process.threadById(payload.tid);
188        if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) {
189            var timestamp = payload.ts / 1000;
190            // We do allow records for unrelated threads to arrive out-of-order,
191            // so there's a chance we're getting records from the past.
192            if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime))
193                this._minimumRecordTime = timestamp;
194            if (!this._maximumRecordTime || timestamp > this._maximumRecordTime)
195                this._maximumRecordTime = timestamp;
196            var event = thread.addEvent(payload);
197            if (payload.ph === WebInspector.TracingModel.Phase.SnapshotObject)
198                process.addObject(event);
199            if (event && event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage &&
200                event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory &&
201                event.args["sessionId"] === this._sessionId)
202                this._devtoolsMetadataEvents.push(event);
203            return;
204        }
205        switch (payload.name) {
206        case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex:
207            process._setSortIndex(payload.args["sort_index"]);
208            break;
209        case WebInspector.TracingModel.MetadataEvent.ProcessName:
210            process._setName(payload.args["name"]);
211            break;
212        case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex:
213            thread._setSortIndex(payload.args["sort_index"]);
214            break;
215        case WebInspector.TracingModel.MetadataEvent.ThreadName:
216            thread._setName(payload.args["name"]);
217            break;
218        }
219    },
220
221    /**
222     * @return {number}
223     */
224    minimumRecordTime: function()
225    {
226        return this._minimumRecordTime;
227    },
228
229    /**
230     * @return {number}
231     */
232    maximumRecordTime: function()
233    {
234        return this._maximumRecordTime;
235    },
236
237    /**
238     * @return {!Array.<!WebInspector.TracingModel.Process>}
239     */
240    sortedProcesses: function()
241    {
242        return WebInspector.TracingModel.NamedObject._sort(Object.values(this._processById));
243    },
244
245    __proto__: WebInspector.TargetAwareObject.prototype
246}
247
248/**
249 * @constructor
250 * @param {!WebInspector.TracingModel.EventPayload} payload
251 * @param {number} level
252 * @param {?WebInspector.TracingModel.Thread} thread
253 */
254WebInspector.TracingModel.Event = function(payload, level, thread)
255{
256    this.name = payload.name;
257    this.category = payload.cat;
258    this.startTime = payload.ts / 1000;
259    this.args = payload.args;
260    this.phase = payload.ph;
261    this.level = level;
262
263    if (payload.dur)
264        this._setEndTime((payload.ts + payload.dur) / 1000);
265
266    if (payload.id)
267        this.id = payload.id;
268
269    this.thread = thread;
270
271    /** @type {?string} */
272    this.warning = null;
273    /** @type {?WebInspector.TracingModel.Event} */
274    this.initiator = null;
275    /** @type {?Array.<!ConsoleAgent.CallFrame>} */
276    this.stackTrace = null;
277    /** @type {?Element} */
278    this.previewElement = null;
279    /** @type {?string} */
280    this.imageURL = null;
281    /** @type {number} */
282    this.backendNodeId = 0;
283
284    /** @type {number} */
285    this.selfTime = 0;
286}
287
288WebInspector.TracingModel.Event.prototype = {
289    /**
290     * @param {number} endTime
291     */
292    _setEndTime: function(endTime)
293    {
294        if (endTime < this.startTime) {
295            console.assert(false, "Event out of order: " + this.name);
296            return;
297        }
298        this.endTime = endTime;
299        this.duration = endTime - this.startTime;
300    },
301
302    /**
303     * @param {!WebInspector.TracingModel.EventPayload} payload
304     */
305    _complete: function(payload)
306    {
307        if (this.name !== payload.name) {
308            console.assert(false, "Open/close event mismatch: " + this.name + " vs. " + payload.name);
309            return;
310        }
311        if (payload.args) {
312            for (var name in payload.args) {
313                if (name in this.args)
314                    console.error("Same argument name (" + name +  ") is used for begin and end phases of " + this.name);
315                this.args[name] = payload.args[name];
316            }
317        }
318        this._setEndTime(payload.ts / 1000);
319    }
320}
321
322/**
323 * @param {!WebInspector.TracingModel.Event} a
324 * @param {!WebInspector.TracingModel.Event} b
325 * @return {number}
326 */
327WebInspector.TracingModel.Event.compareStartTime = function (a, b)
328{
329    return a.startTime - b.startTime;
330}
331
332/**
333 * @constructor
334 */
335WebInspector.TracingModel.NamedObject = function()
336{
337}
338
339WebInspector.TracingModel.NamedObject.prototype =
340{
341    /**
342     * @param {string} name
343     */
344    _setName: function(name)
345    {
346        this._name = name;
347    },
348
349    /**
350     * @return {string}
351     */
352    name: function()
353    {
354        return this._name;
355    },
356
357    /**
358     * @param {number} sortIndex
359     */
360    _setSortIndex: function(sortIndex)
361    {
362        this._sortIndex = sortIndex;
363    },
364}
365
366/**
367 * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array
368 */
369WebInspector.TracingModel.NamedObject._sort = function(array)
370{
371    /**
372     * @param {!WebInspector.TracingModel.NamedObject} a
373     * @param {!WebInspector.TracingModel.NamedObject} b
374     */
375    function comparator(a, b)
376    {
377        return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.name().localeCompare(b.name());
378    }
379    return array.sort(comparator);
380}
381
382/**
383 * @constructor
384 * @extends {WebInspector.TracingModel.NamedObject}
385 * @param {number} id
386 */
387WebInspector.TracingModel.Process = function(id)
388{
389    WebInspector.TracingModel.NamedObject.call(this);
390    this._setName("Process " + id);
391    this._threads = {};
392    this._objects = {};
393}
394
395WebInspector.TracingModel.Process.prototype = {
396    /**
397     * @param {number} id
398     * @return {!WebInspector.TracingModel.Thread}
399     */
400    threadById: function(id)
401    {
402        var thread = this._threads[id];
403        if (!thread) {
404            thread = new WebInspector.TracingModel.Thread(this, id);
405            this._threads[id] = thread;
406        }
407        return thread;
408    },
409
410    /**
411     * @param {!WebInspector.TracingModel.Event} event
412     */
413    addObject: function(event)
414    {
415        this.objectsByName(event.name).push(event);
416    },
417
418    /**
419     * @param {string} name
420     * @return {!Array.<!WebInspector.TracingModel.Event>}
421     */
422    objectsByName: function(name)
423    {
424        var objects = this._objects[name];
425        if (!objects) {
426            objects = [];
427            this._objects[name] = objects;
428        }
429        return objects;
430    },
431
432    /**
433     * @return {!Array.<string>}
434     */
435    sortedObjectNames: function()
436    {
437        return Object.keys(this._objects).sort();
438    },
439
440    /**
441     * @return {!Array.<!WebInspector.TracingModel.Thread>}
442     */
443    sortedThreads: function()
444    {
445        return WebInspector.TracingModel.NamedObject._sort(Object.values(this._threads));
446    },
447
448    __proto__: WebInspector.TracingModel.NamedObject.prototype
449}
450
451/**
452 * @constructor
453 * @extends {WebInspector.TracingModel.NamedObject}
454 * @param {!WebInspector.TracingModel.Process} process
455 * @param {number} id
456 */
457WebInspector.TracingModel.Thread = function(process, id)
458{
459    WebInspector.TracingModel.NamedObject.call(this);
460    this._process = process;
461    this._setName("Thread " + id);
462    this._events = [];
463    this._stack = [];
464    this._maxStackDepth = 0;
465}
466
467WebInspector.TracingModel.Thread.prototype = {
468    /**
469     * @param {!WebInspector.TracingModel.EventPayload} payload
470     * @return {?WebInspector.TracingModel.Event} event
471     */
472    addEvent: function(payload)
473    {
474        for (var top = this._stack.peekLast(); top && top.endTime && top.endTime <= payload.ts / 1000;) {
475            this._stack.pop();
476            top = this._stack.peekLast();
477        }
478        if (payload.ph === WebInspector.TracingModel.Phase.End) {
479            var openEvent = this._stack.pop();
480            // Quietly ignore unbalanced close events, they're legit (we could have missed start one).
481            if (openEvent)
482                openEvent._complete(payload);
483            return null;
484        }
485
486        var event = new WebInspector.TracingModel.Event(payload, this._stack.length, this);
487        if (payload.ph === WebInspector.TracingModel.Phase.Begin || payload.ph === WebInspector.TracingModel.Phase.Complete) {
488            this._stack.push(event);
489            if (this._maxStackDepth < this._stack.length)
490                this._maxStackDepth = this._stack.length;
491        }
492        if (this._events.length && this._events.peekLast().startTime > event.startTime)
493            console.assert(false, "Event is our of order: " + event.name);
494        this._events.push(event);
495        return event;
496    },
497
498    /**
499     * @return {!WebInspector.TracingModel.Process}
500     */
501    process: function()
502    {
503        return this._process;
504    },
505
506    /**
507     * @return {!Array.<!WebInspector.TracingModel.Event>}
508     */
509    events: function()
510    {
511        return this._events;
512    },
513
514    /**
515     * @return {number}
516     */
517    maxStackDepth: function()
518    {
519        // Reserve one for non-container events.
520        return this._maxStackDepth + 1;
521    },
522
523    __proto__: WebInspector.TracingModel.NamedObject.prototype
524}
525
526
527/**
528 * @constructor
529 * @implements {TracingAgent.Dispatcher}
530 * @param {!WebInspector.TracingModel} tracingModel
531 */
532WebInspector.TracingDispatcher = function(tracingModel)
533{
534    this._tracingModel = tracingModel;
535}
536
537WebInspector.TracingDispatcher.prototype = {
538    /**
539     * @param {number} usage
540     */
541    bufferUsage: function(usage)
542    {
543        this._tracingModel._bufferUsage(usage);
544    },
545
546    /**
547     * @param {!Array.<!WebInspector.TracingModel.EventPayload>} data
548     */
549    dataCollected: function(data)
550    {
551        this._tracingModel._eventsCollected(data);
552    },
553
554    tracingComplete: function()
555    {
556        this._tracingModel._tracingComplete();
557    }
558}
559