• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
10WebInspector.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
20WebInspector.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
97WebInspector.TracingTimelineModel.defaultTracingCategoryFilter = "*,disabled-by-default-cc.debug,disabled-by-default-devtools.timeline,disabled-by-default-devtools.timeline.frame";
98
99WebInspector.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 */
476WebInspector.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
488WebInspector.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