Home | History | Annotate | Download | only in timeline
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @param {!WebInspector.TracingModel} tracingModel
      7  * @constructor
      8  * @extends {WebInspector.TimelineModel}
      9  */
     10 WebInspector.TracingTimelineModel = function(tracingModel)
     11 {
     12     WebInspector.TimelineModel.call(this, tracingModel.target());
     13     this._tracingModel = tracingModel;
     14     this._mainThreadEvents = [];
     15     this._inspectedTargetEvents = [];
     16 
     17     this.reset();
     18 }
     19 
     20 WebInspector.TracingTimelineModel.RecordType = {
     21     Program: "Program",
     22     EventDispatch: "EventDispatch",
     23 
     24     GPUTask: "GPUTask",
     25 
     26     RequestMainThreadFrame: "RequestMainThreadFrame",
     27     BeginFrame: "BeginFrame",
     28     BeginMainThreadFrame: "BeginMainThreadFrame",
     29     ActivateLayerTree: "ActivateLayerTree",
     30     DrawFrame: "DrawFrame",
     31     ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
     32     RecalculateStyles: "RecalculateStyles",
     33     InvalidateLayout: "InvalidateLayout",
     34     Layout: "Layout",
     35     UpdateLayer: "UpdateLayer",
     36     PaintSetup: "PaintSetup",
     37     Paint: "Paint",
     38     PaintImage: "PaintImage",
     39     Rasterize: "Rasterize",
     40     RasterTask: "RasterTask",
     41     ScrollLayer: "ScrollLayer",
     42     CompositeLayers: "CompositeLayers",
     43 
     44     ParseHTML: "ParseHTML",
     45 
     46     TimerInstall: "TimerInstall",
     47     TimerRemove: "TimerRemove",
     48     TimerFire: "TimerFire",
     49 
     50     XHRReadyStateChange: "XHRReadyStateChange",
     51     XHRLoad: "XHRLoad",
     52     EvaluateScript: "EvaluateScript",
     53 
     54     MarkLoad: "MarkLoad",
     55     MarkDOMContent: "MarkDOMContent",
     56     MarkFirstPaint: "MarkFirstPaint",
     57 
     58     TimeStamp: "TimeStamp",
     59     ConsoleTime: "ConsoleTime",
     60 
     61     ResourceSendRequest: "ResourceSendRequest",
     62     ResourceReceiveResponse: "ResourceReceiveResponse",
     63     ResourceReceivedData: "ResourceReceivedData",
     64     ResourceFinish: "ResourceFinish",
     65 
     66     FunctionCall: "FunctionCall",
     67     GCEvent: "GCEvent",
     68     JSFrame: "JSFrame",
     69 
     70     UpdateCounters: "UpdateCounters",
     71 
     72     RequestAnimationFrame: "RequestAnimationFrame",
     73     CancelAnimationFrame: "CancelAnimationFrame",
     74     FireAnimationFrame: "FireAnimationFrame",
     75 
     76     WebSocketCreate : "WebSocketCreate",
     77     WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
     78     WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
     79     WebSocketDestroy : "WebSocketDestroy",
     80 
     81     EmbedderCallback : "EmbedderCallback",
     82 
     83     CallStack: "CallStack",
     84     SetLayerTreeId: "SetLayerTreeId",
     85     TracingStartedInPage: "TracingStartedInPage",
     86 
     87     DecodeImage: "Decode Image",
     88     ResizeImage: "Resize Image",
     89     DrawLazyPixelRef: "Draw LazyPixelRef",
     90     DecodeLazyPixelRef: "Decode LazyPixelRef",
     91 
     92     LazyPixelRef: "LazyPixelRef",
     93     LayerTreeHostImplSnapshot: "cc::LayerTreeHostImpl",
     94     PictureSnapshot: "cc::Picture"
     95 };
     96 
     97 WebInspector.TracingTimelineModel.defaultTracingCategoryFilter = "*,disabled-by-default-cc.debug,disabled-by-default-devtools.timeline,disabled-by-default-devtools.timeline.frame";
     98 
     99 WebInspector.TracingTimelineModel.prototype = {
    100     /**
    101      * @param {boolean} captureStacks
    102      * @param {boolean} captureMemory
    103      * @param {boolean} capturePictures
    104      */
    105     startRecording: function(captureStacks, captureMemory, capturePictures)
    106     {
    107         var categories;
    108         if (WebInspector.experimentsSettings.timelineTracingMode.isEnabled()) {
    109             categories = WebInspector.TracingTimelineModel.defaultTracingCategoryFilter;
    110         } else {
    111             var categoriesArray = ["disabled-by-default-devtools.timeline", "disabled-by-default-devtools.timeline.frame", "devtools"];
    112             if (captureStacks)
    113                 categoriesArray.push("disabled-by-default-devtools.timeline.stack");
    114             if (capturePictures)
    115                 categoriesArray.push("disabled-by-default-devtools.timeline.layers", "disabled-by-default-devtools.timeline.picture");
    116             categories = categoriesArray.join(",");
    117         }
    118         this._startRecordingWithCategories(categories);
    119     },
    120 
    121     stopRecording: function()
    122     {
    123         this._tracingModel.stop(this._didStopRecordingTraceEvents.bind(this));
    124     },
    125 
    126     /**
    127      * @param {string} sessionId
    128      * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
    129      */
    130     setEventsForTest: function(sessionId, events)
    131     {
    132         this.reset();
    133         this._didStartRecordingTraceEvents();
    134         this._tracingModel.setEventsForTest(sessionId, events);
    135         this._didStopRecordingTraceEvents();
    136     },
    137 
    138     /**
    139      * @param {string} categories
    140      */
    141     _startRecordingWithCategories: function(categories)
    142     {
    143         this.reset();
    144         this._tracingModel.start(categories, "", this._didStartRecordingTraceEvents.bind(this));
    145     },
    146 
    147     _didStartRecordingTraceEvents: function()
    148     {
    149         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
    150     },
    151 
    152     _didStopRecordingTraceEvents: function()
    153     {
    154         var events = this._tracingModel.devtoolsMetadataEvents();
    155         events.sort(WebInspector.TracingModel.Event.compareStartTime);
    156 
    157         this._resetProcessingState();
    158         for (var i = 0, length = events.length; i < length; i++) {
    159             var event = events[i];
    160             var process = event.thread.process();
    161             var startTime = event.startTime;
    162 
    163             var endTime = Infinity;
    164             if (i + 1 < length)
    165                 endTime = events[i + 1].startTime;
    166 
    167             process.sortedThreads().forEach(this._processThreadEvents.bind(this, startTime, endTime, event.thread));
    168         }
    169         this._resetProcessingState();
    170 
    171         this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
    172 
    173         this._buildTimelineRecords();
    174         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
    175     },
    176 
    177     /**
    178      * @return {number}
    179      */
    180     minimumRecordTime: function()
    181     {
    182         return this._tracingModel.minimumRecordTime();
    183     },
    184 
    185     /**
    186      * @return {number}
    187      */
    188     maximumRecordTime: function()
    189     {
    190         return this._tracingModel.maximumRecordTime();
    191     },
    192 
    193     /**
    194      * @return {!Array.<!WebInspector.TracingModel.Event>}
    195      */
    196     inspectedTargetEvents: function()
    197     {
    198         return this._inspectedTargetEvents;
    199     },
    200 
    201     /**
    202      * @return {!Array.<!WebInspector.TracingModel.Event>}
    203      */
    204     mainThreadEvents: function()
    205     {
    206         return this._mainThreadEvents;
    207     },
    208 
    209     reset: function()
    210     {
    211         this._mainThreadEvents = [];
    212         this._inspectedTargetEvents = [];
    213         WebInspector.TimelineModel.prototype.reset.call(this);
    214     },
    215 
    216     _buildTimelineRecords: function()
    217     {
    218         var recordStack = [];
    219         var mainThreadEvents = this._mainThreadEvents;
    220         for (var i = 0, size = mainThreadEvents.length; i < size; ++i) {
    221             var event = mainThreadEvents[i];
    222             while (recordStack.length) {
    223                 var top = recordStack.peekLast();
    224                 if (top._event.endTime >= event.startTime)
    225                     break;
    226                 recordStack.pop();
    227             }
    228             var parentRecord = recordStack.peekLast() || null;
    229             var record = new WebInspector.TracingTimelineModel.TraceEventRecord(this, event, parentRecord);
    230             if (WebInspector.TracingTimelineUIUtils.isEventDivider(record))
    231                 this._eventDividerRecords.push(record);
    232             if (!recordStack.length)
    233                 this._addTopLevelRecord(record);
    234             if (event.endTime)
    235                 recordStack.push(record);
    236         }
    237     },
    238 
    239     /**
    240      * @param {!WebInspector.TracingTimelineModel.TraceEventRecord} record
    241      */
    242     _addTopLevelRecord: function(record)
    243     {
    244         this._updateBoundaries(record);
    245         this._records.push(record);
    246         if (record.type() === WebInspector.TimelineModel.RecordType.Program)
    247             this._mainThreadTasks.push(record);
    248         if (record.type() === WebInspector.TimelineModel.RecordType.GPUTask)
    249             this._gpuThreadTasks.push(record);
    250         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
    251     },
    252 
    253     _resetProcessingState: function()
    254     {
    255         this._sendRequestEvents = {};
    256         this._timerEvents = {};
    257         this._requestAnimationFrameEvents = {};
    258         this._layoutInvalidate = {};
    259         this._lastScheduleStyleRecalculation = {};
    260         this._webSocketCreateEvents = {};
    261         this._paintImageEventByPixelRefId = {};
    262         this._lastPaintForLayer = {};
    263         this._lastRecalculateStylesEvent = null;
    264         this._currentScriptEvent = null;
    265         this._eventStack = [];
    266     },
    267 
    268     /**
    269      * @param {number} startTime
    270      * @param {?number} endTime
    271      * @param {!WebInspector.TracingModel.Thread} mainThread
    272      * @param {!WebInspector.TracingModel.Thread} thread
    273      */
    274     _processThreadEvents: function(startTime, endTime, mainThread, thread)
    275     {
    276         var events = thread.events();
    277         var length = events.length;
    278         var i = events.lowerBound(startTime, function (time, event) { return time - event.startTime });
    279 
    280         this._eventStack = [];
    281         for (; i < length; i++) {
    282             var event = events[i];
    283             if (endTime && event.startTime >= endTime)
    284                 break;
    285             this._processEvent(event);
    286             if (thread === mainThread)
    287                 this._mainThreadEvents.push(event);
    288             this._inspectedTargetEvents.push(event);
    289         }
    290     },
    291 
    292     /**
    293      * @param {!WebInspector.TracingModel.Event} event
    294      */
    295     _processEvent: function(event)
    296     {
    297         var recordTypes = WebInspector.TracingTimelineModel.RecordType;
    298 
    299         var eventStack = this._eventStack;
    300         while (eventStack.length && eventStack.peekLast().endTime < event.startTime)
    301             eventStack.pop();
    302         var duration = event.duration;
    303         if (duration) {
    304             if (eventStack.length) {
    305                 var parent = eventStack.peekLast();
    306                 parent.selfTime -= duration;
    307             }
    308             event.selfTime = duration;
    309             eventStack.push(event);
    310         }
    311 
    312         if (this._currentScriptEvent && event.startTime > this._currentScriptEvent.endTime)
    313             this._currentScriptEvent = null;
    314 
    315         switch (event.name) {
    316         case recordTypes.CallStack:
    317             var lastMainThreadEvent = this._mainThreadEvents.peekLast();
    318             if (lastMainThreadEvent && event.args.stack && event.args.stack.length)
    319                 lastMainThreadEvent.stackTrace = event.args.stack;
    320             break;
    321 
    322         case recordTypes.ResourceSendRequest:
    323             this._sendRequestEvents[event.args.data["requestId"]] = event;
    324             event.imageURL = event.args.data["url"];
    325             break;
    326 
    327         case recordTypes.ResourceReceiveResponse:
    328         case recordTypes.ResourceReceivedData:
    329         case recordTypes.ResourceFinish:
    330             event.initiator = this._sendRequestEvents[event.args.data["requestId"]];
    331             if (event.initiator)
    332                 event.imageURL = event.initiator.imageURL;
    333             break;
    334 
    335         case recordTypes.TimerInstall:
    336             this._timerEvents[event.args.data["timerId"]] = event;
    337             break;
    338 
    339         case recordTypes.TimerFire:
    340             event.initiator = this._timerEvents[event.args.data["timerId"]];
    341             break;
    342 
    343         case recordTypes.RequestAnimationFrame:
    344             this._requestAnimationFrameEvents[event.args.data["id"]] = event;
    345             break;
    346 
    347         case recordTypes.FireAnimationFrame:
    348             event.initiator = this._requestAnimationFrameEvents[event.args.data["id"]];
    349             break;
    350 
    351         case recordTypes.ScheduleStyleRecalculation:
    352             this._lastScheduleStyleRecalculation[event.args.frame] = event;
    353             break;
    354 
    355         case recordTypes.RecalculateStyles:
    356             event.initiator = this._lastScheduleStyleRecalculation[event.args.frame];
    357             this._lastRecalculateStylesEvent = event;
    358             break;
    359 
    360         case recordTypes.InvalidateLayout:
    361             // Consider style recalculation as a reason for layout invalidation,
    362             // but only if we had no earlier layout invalidation records.
    363             var layoutInitator = event;
    364             var frameId = event.args.frame;
    365             if (!this._layoutInvalidate[frameId] && this._lastRecalculateStylesEvent && this._lastRecalculateStylesEvent.endTime >  event.startTime)
    366                 layoutInitator = this._lastRecalculateStylesEvent.initiator;
    367             this._layoutInvalidate[frameId] = layoutInitator;
    368             break;
    369 
    370         case recordTypes.Layout:
    371             var frameId = event.args["beginData"]["frame"];
    372             event.initiator = this._layoutInvalidate[frameId];
    373             event.backendNodeId = event.args["endData"]["rootNode"];
    374             event.highlightQuad =  event.args["endData"]["root"];
    375             this._layoutInvalidate[frameId] = null;
    376             if (this._currentScriptEvent)
    377                 event.warning = WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck.");
    378             break;
    379 
    380         case recordTypes.WebSocketCreate:
    381             this._webSocketCreateEvents[event.args.data["identifier"]] = event;
    382             break;
    383 
    384         case recordTypes.WebSocketSendHandshakeRequest:
    385         case recordTypes.WebSocketReceiveHandshakeResponse:
    386         case recordTypes.WebSocketDestroy:
    387             event.initiator = this._webSocketCreateEvents[event.args.data["identifier"]];
    388             break;
    389 
    390         case recordTypes.EvaluateScript:
    391         case recordTypes.FunctionCall:
    392             if (!this._currentScriptEvent)
    393                 this._currentScriptEvent = event;
    394             break;
    395 
    396         case recordTypes.SetLayerTreeId:
    397             this._inspectedTargetLayerTreeId = event.args["layerTreeId"];
    398             break;
    399 
    400         case recordTypes.Paint:
    401             event.highlightQuad = event.args["data"]["clip"];
    402             event.backendNodeId = event.args["data"]["nodeId"];
    403             var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
    404             if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
    405                 break;
    406             this._lastPaintForLayer[layerUpdateEvent.args["layerId"]] = event;
    407             break;
    408 
    409         case recordTypes.PictureSnapshot:
    410             var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
    411             if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
    412                 break;
    413             var paintEvent = this._lastPaintForLayer[layerUpdateEvent.args["layerId"]];
    414             if (!paintEvent)
    415                 break;
    416             paintEvent.picture = event.args["snapshot"]["skp64"];
    417             break;
    418 
    419         case recordTypes.ScrollLayer:
    420             event.backendNodeId = event.args["data"]["nodeId"];
    421             break;
    422 
    423         case recordTypes.PaintImage:
    424             event.backendNodeId = event.args["data"]["nodeId"];
    425             event.imageURL = event.args["data"]["url"];
    426             break;
    427 
    428         case recordTypes.DecodeImage:
    429         case recordTypes.ResizeImage:
    430             var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
    431             if (!paintImageEvent) {
    432                 var decodeLazyPixelRefEvent = this._findAncestorEvent(recordTypes.DecodeLazyPixelRef);
    433                 paintImageEvent = decodeLazyPixelRefEvent && this._paintImageEventByPixelRefId[decodeLazyPixelRefEvent.args["LazyPixelRef"]];
    434             }
    435             if (!paintImageEvent)
    436                 break;
    437             event.backendNodeId = paintImageEvent.backendNodeId;
    438             event.imageURL = paintImageEvent.imageURL;
    439             break;
    440 
    441         case recordTypes.DrawLazyPixelRef:
    442             var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
    443             if (!paintImageEvent)
    444                 break;
    445             this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]] = paintImageEvent;
    446             event.backendNodeId = paintImageEvent.backendNodeId;
    447             event.imageURL = paintImageEvent.imageURL;
    448             break;
    449         }
    450     },
    451 
    452     /**
    453      * @param {string} name
    454      * @return {?WebInspector.TracingModel.Event}
    455      */
    456     _findAncestorEvent: function(name)
    457     {
    458         for (var i = this._eventStack.length - 1; i >= 0; --i) {
    459             var event = this._eventStack[i];
    460             if (event.name === name)
    461                 return event;
    462         }
    463         return null;
    464     },
    465 
    466     __proto__: WebInspector.TimelineModel.prototype
    467 }
    468 
    469 /**
    470  * @constructor
    471  * @implements {WebInspector.TimelineModel.Record}
    472  * @param {!WebInspector.TimelineModel} model
    473  * @param {!WebInspector.TracingModel.Event} traceEvent
    474  * @param {?WebInspector.TracingTimelineModel.TraceEventRecord} parentRecord
    475  */
    476 WebInspector.TracingTimelineModel.TraceEventRecord = function(model, traceEvent, parentRecord)
    477 {
    478     this._model = model;
    479     this._event = traceEvent;
    480     traceEvent._timelineRecord = this;
    481     if (parentRecord) {
    482         this.parent = parentRecord;
    483         parentRecord._children.push(this);
    484     }
    485     this._children = [];
    486 }
    487 
    488 WebInspector.TracingTimelineModel.TraceEventRecord.prototype = {
    489     /**
    490      * @return {?Array.<!ConsoleAgent.CallFrame>}
    491      */
    492     callSiteStackTrace: function()
    493     {
    494         var initiator = this._event.initiator;
    495         return initiator ? initiator.stackTrace : null;
    496     },
    497 
    498     /**
    499      * @return {?WebInspector.TimelineModel.Record}
    500      */
    501     initiator: function()
    502     {
    503         var initiator = this._event.initiator;
    504         return initiator ? initiator._timelineRecord : null;
    505     },
    506 
    507     /**
    508      * @return {!WebInspector.Target}
    509      */
    510     target: function()
    511     {
    512         return this._model.target();
    513     },
    514 
    515     /**
    516      * @return {number}
    517      */
    518     selfTime: function()
    519     {
    520         return this._event.selfTime;
    521     },
    522 
    523     /**
    524      * @return {!Array.<!WebInspector.TimelineModel.Record>}
    525      */
    526     children: function()
    527     {
    528         return this._children;
    529     },
    530 
    531     /**
    532      * @return {!WebInspector.TimelineCategory}
    533      */
    534     category: function()
    535     {
    536         var style = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(this._event.name);
    537         return style.category;
    538     },
    539 
    540     /**
    541      * @return {number}
    542      */
    543     startTime: function()
    544     {
    545         return this._event.startTime;
    546     },
    547 
    548     /**
    549      * @return {string|undefined}
    550      */
    551     thread: function()
    552     {
    553         return "CPU";
    554     },
    555 
    556     /**
    557      * @return {number}
    558      */
    559     endTime: function()
    560     {
    561         return this._event.endTime || this._event.startTime;
    562     },
    563 
    564     /**
    565      * @param {number} endTime
    566      */
    567     setEndTime: function(endTime)
    568     {
    569         throw new Error("Unsupported operation setEndTime");
    570     },
    571 
    572     /**
    573      * @return {!Object}
    574      */
    575     data: function()
    576     {
    577         return this._event.args.data;
    578     },
    579 
    580     /**
    581      * @return {string}
    582      */
    583     type: function()
    584     {
    585         return this._event.name;
    586     },
    587 
    588     /**
    589      * @return {string}
    590      */
    591     frameId: function()
    592     {
    593         switch (this._event.name) {
    594         case WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation:
    595         case WebInspector.TracingTimelineModel.RecordType.RecalculateStyles:
    596         case WebInspector.TracingTimelineModel.RecordType.InvalidateLayout:
    597             return this._event.args["frameId"];
    598         case WebInspector.TracingTimelineModel.RecordType.Layout:
    599             return this._event.args["beginData"]["frameId"];
    600         default:
    601             var data = this._event.args.data;
    602             return (data && data["frame"]) || "";
    603         }
    604     },
    605 
    606     /**
    607      * @return {?Array.<!ConsoleAgent.CallFrame>}
    608      */
    609     stackTrace: function()
    610     {
    611         return this._event.stackTrace;
    612     },
    613 
    614     /**
    615      * @param {string} key
    616      * @return {?Object}
    617      */
    618     getUserObject: function(key)
    619     {
    620         if (key === "TimelineUIUtils::preview-element")
    621             return this._event.previewElement;
    622         throw new Error("Unexpected key: " + key);
    623     },
    624 
    625     /**
    626      * @param {string} key
    627      * @param {?Object|undefined} value
    628      */
    629     setUserObject: function(key, value)
    630     {
    631         if (key !== "TimelineUIUtils::preview-element")
    632             throw new Error("Unexpected key: " + key);
    633         this._event.previewElement = /** @type {?Element} */ (value);
    634     },
    635 
    636     /**
    637      * @return {!Object.<string, number>}
    638      */
    639     aggregatedStats: function()
    640     {
    641         return {};
    642     },
    643 
    644     /**
    645      * @return {?Array.<string>}
    646      */
    647     warnings: function()
    648     {
    649         if (this._event.warning)
    650             return [this._event.warning];
    651         return null;
    652     },
    653 
    654     /**
    655      * @return {!WebInspector.TracingModel.Event}
    656      */
    657     traceEvent: function()
    658     {
    659         return this._event;
    660     }
    661 }
    662