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  * @extends {WebInspector.TargetAwareObject}
     10  */
     11 WebInspector.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 
     19 WebInspector.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  */
     36 WebInspector.TracingModel.EventPayload;
     37 
     38 /**
     39  * @enum {string}
     40  */
     41 WebInspector.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 
     61 WebInspector.TracingModel.MetadataEvent = {
     62     ProcessSortIndex: "process_sort_index",
     63     ProcessName: "process_name",
     64     ThreadSortIndex: "thread_sort_index",
     65     ThreadName: "thread_name"
     66 }
     67 
     68 WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline";
     69 
     70 WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools";
     71 
     72 WebInspector.TracingModel.DevToolsMetadataEvent = {
     73     TracingStartedInPage: "TracingStartedInPage",
     74 };
     75 
     76 WebInspector.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  */
    254 WebInspector.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 
    288 WebInspector.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  */
    327 WebInspector.TracingModel.Event.compareStartTime = function (a, b)
    328 {
    329     return a.startTime - b.startTime;
    330 }
    331 
    332 /**
    333  * @constructor
    334  */
    335 WebInspector.TracingModel.NamedObject = function()
    336 {
    337 }
    338 
    339 WebInspector.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  */
    369 WebInspector.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  */
    387 WebInspector.TracingModel.Process = function(id)
    388 {
    389     WebInspector.TracingModel.NamedObject.call(this);
    390     this._setName("Process " + id);
    391     this._threads = {};
    392     this._objects = {};
    393 }
    394 
    395 WebInspector.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  */
    457 WebInspector.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 
    467 WebInspector.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  */
    532 WebInspector.TracingDispatcher = function(tracingModel)
    533 {
    534     this._tracingModel = tracingModel;
    535 }
    536 
    537 WebInspector.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