• 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 * @implements {WebInspector.TimelineModeView}
10 * @implements {WebInspector.FlameChartDelegate}
11 * @extends {WebInspector.VBox}
12 * @param {!WebInspector.TimelineModeViewDelegate} delegate
13 * @param {!WebInspector.TracingModel} tracingModel
14 * @param {!WebInspector.TimelineModel} modelForMinimumBoundary
15 */
16WebInspector.TimelineTracingView = function(delegate, tracingModel, modelForMinimumBoundary)
17{
18    WebInspector.VBox.call(this);
19    this._delegate = delegate;
20    this._tracingModel = tracingModel;
21    this.element.classList.add("timeline-flamechart");
22    this.registerRequiredCSS("flameChart.css");
23    this._dataProvider = new WebInspector.TraceViewFlameChartDataProvider(this._tracingModel, modelForMinimumBoundary);
24    this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true);
25    this._mainView.show(this.element);
26    this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
27}
28
29WebInspector.TimelineTracingView.prototype = {
30    /**
31     * @param {number} windowStartTime
32     * @param {number} windowEndTime
33     */
34    requestWindowTimes: function(windowStartTime, windowEndTime)
35    {
36        this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
37    },
38
39    wasShown: function()
40    {
41        this._mainView._scheduleUpdate();
42    },
43
44    /**
45     * @return {!WebInspector.View}
46     */
47    view: function()
48    {
49        return this;
50    },
51
52    dispose: function()
53    {
54    },
55
56    reset: function()
57    {
58        this._dataProvider.reset();
59        this._mainView.setWindowTimes(0, Infinity);
60    },
61
62    /**
63     * @param {?RegExp} textFilter
64     */
65    refreshRecords: function(textFilter)
66    {
67        this._dataProvider.reset();
68        this._mainView._scheduleUpdate();
69    },
70
71    /**
72     * @param {!WebInspector.TimelineModel.Record} record
73     */
74    addRecord: function(record) {},
75
76    /**
77     * @param {?WebInspector.TimelineModel.Record} record
78     * @param {string=} regex
79     * @param {boolean=} selectRecord
80     */
81    highlightSearchResult: function(record, regex, selectRecord) {},
82
83    /**
84     * @param {number} startTime
85     * @param {number} endTime
86     */
87    setWindowTimes: function(startTime, endTime)
88    {
89        this._mainView.setWindowTimes(startTime, endTime);
90    },
91
92    /**
93     * @param {number} width
94     */
95    setSidebarSize: function(width) {},
96
97    /**
98     * @param {?WebInspector.TimelineSelection} selection
99     */
100    setSelection: function(selection) {},
101
102    /**
103     * @param {!WebInspector.Event} event
104     */
105    _onEntrySelected: function(event)
106    {
107        var index = /** @type {number} */ (event.data);
108        var record = this._dataProvider._recordAt(index);
109        if (!record || this._dataProvider._isHeaderRecord(record)) {
110            this._delegate.showInDetails("", document.createTextNode(""));
111            return;
112        }
113        var contentHelper = new WebInspector.TimelineDetailsContentHelper(null, null, false);
114        contentHelper.appendTextRow(WebInspector.UIString("Name"), record.name);
115        contentHelper.appendTextRow(WebInspector.UIString("Category"), record.category);
116        contentHelper.appendTextRow(WebInspector.UIString("Start"), Number.millisToString(record.startTime - this._tracingModel.minimumRecordTime(), true));
117        contentHelper.appendTextRow(WebInspector.UIString("Duration"), Number.millisToString(record.duration, true));
118        if (!Object.isEmpty(record.args))
119            contentHelper.appendElementRow(WebInspector.UIString("Arguments"), this._formatArguments(record.args));
120        /**
121         * @this {WebInspector.TimelineTracingView}
122         */
123        function reveal()
124        {
125            WebInspector.Revealer.reveal(new WebInspector.DeferredTracingLayerTree(this._tracingModel.target(), record.args["snapshot"]["active_tree"]["root_layer"], record.args["snapshot"]["device_viewport_size"]));
126        }
127        /**
128         * @param {!Node=} node
129         * @this {WebInspector.TimelineTracingView}
130         */
131        function appendPreviewAndshowDetails(node)
132        {
133            if (node)
134                contentHelper.appendElementRow("Preview", node);
135            this._delegate.showInDetails(WebInspector.UIString("Selected Event"), contentHelper.element);
136        }
137        var recordTypes = WebInspector.TracingTimelineModel.RecordType;
138        switch (record.name) {
139        case recordTypes.PictureSnapshot:
140            WebInspector.TracingTimelineUIUtils._buildPicturePreviewContent(record.args["snapshot"]["skp64"], appendPreviewAndshowDetails.bind(this));
141            break;
142        case recordTypes.LayerTreeHostImplSnapshot:
143            var link = document.createElement("span");
144            link.classList.add("revealable-link");
145            link.textContent = "show";
146            link.addEventListener("click", reveal.bind(this), false);
147            contentHelper.appendElementRow(WebInspector.UIString("Layer tree"), link);
148            // Fall-through intended.
149        default:
150            this._delegate.showInDetails(WebInspector.UIString("Selected Event"), contentHelper.element);
151        }
152    },
153
154    /**
155     * @param {!Object} args
156     * @return {!Element}
157     */
158    _formatArguments: function(args)
159    {
160        var table = document.createElement("table");
161        for (var name in args) {
162            var row = table.createChild("tr");
163            row.createChild("td", "timeline-details-row-title").textContent = name + ":";
164            var valueContainer = row.createChild("td", "timeline-details-row-data");
165            var value = args[name];
166            if (typeof value === "object" && value) {
167                var localObject = new WebInspector.LocalJSONObject(value);
168                var propertiesSection = new WebInspector.ObjectPropertiesSection(localObject, localObject.description);
169                valueContainer.appendChild(propertiesSection.element);
170            } else {
171                valueContainer.textContent = String(value);
172            }
173        }
174        return table;
175    },
176
177    __proto__: WebInspector.VBox.prototype
178};
179
180/**
181 * @constructor
182 * @implements {WebInspector.FlameChartDataProvider}
183 * @param {!WebInspector.TracingModel} model
184 * @param {!WebInspector.TimelineModel} timelineModelForMinimumBoundary
185 */
186WebInspector.TraceViewFlameChartDataProvider = function(model, timelineModelForMinimumBoundary)
187{
188    WebInspector.FlameChartDataProvider.call(this);
189    this._model = model;
190    this._timelineModelForMinimumBoundary = timelineModelForMinimumBoundary;
191    this._font = "12px " + WebInspector.fontFamily();
192    this._palette = new WebInspector.TraceViewPalette();
193    var dummyEventPayload = {
194        cat: "dummy",
195        pid: 0,
196        tid: 0,
197        ts: 0,
198        ph: "dummy",
199        name: "dummy",
200        args: {},
201        dur: 0,
202        id: 0,
203        s: ""
204    }
205    this._processHeaderRecord = new WebInspector.TracingModel.Event(dummyEventPayload, 0, null);
206    this._threadHeaderRecord = new WebInspector.TracingModel.Event(dummyEventPayload, 0, null);
207}
208
209WebInspector.TraceViewFlameChartDataProvider.prototype = {
210    /**
211     * @return {number}
212     */
213    barHeight: function()
214    {
215        return 20;
216    },
217
218    /**
219     * @return {number}
220     */
221    textBaseline: function()
222    {
223        return 6;
224    },
225
226    /**
227     * @return {number}
228     */
229    textPadding: function()
230    {
231        return 5;
232    },
233
234    /**
235     * @param {number} entryIndex
236     * @return {string}
237     */
238    entryFont: function(entryIndex)
239    {
240        return this._font;
241    },
242
243    /**
244     * @param {number} entryIndex
245     * @return {?string}
246     */
247    entryTitle: function(entryIndex)
248    {
249        var record = this._records[entryIndex];
250        if (this._isHeaderRecord(record))
251            return this._headerTitles[entryIndex]
252        return record.name;
253    },
254
255    /**
256     * @param {number} startTime
257     * @param {number} endTime
258     * @return {?Array.<number>}
259     */
260    dividerOffsets: function(startTime, endTime)
261    {
262        return null;
263    },
264
265    reset: function()
266    {
267        this._timelineData = null;
268        /** @type {!Array.<!WebInspector.TracingModel.Event>} */
269        this._records = [];
270    },
271
272    /**
273     * @return {!WebInspector.FlameChart.TimelineData}
274     */
275    timelineData: function()
276    {
277        if (this._timelineData)
278            return this._timelineData;
279
280        /**
281         * @type {?WebInspector.FlameChart.TimelineData}
282         */
283        this._timelineData = {
284            entryLevels: [],
285            entryTotalTimes: [],
286            entryStartTimes: []
287        };
288
289        this._currentLevel = 0;
290        this._headerTitles = {};
291        this._minimumBoundary = this._timelineModelForMinimumBoundary.minimumRecordTime();
292        this._timeSpan = Math.max(this._model.maximumRecordTime() - this._minimumBoundary, 1000);
293        var processes = this._model.sortedProcesses();
294        for (var processIndex = 0; processIndex < processes.length; ++processIndex) {
295            var process = processes[processIndex];
296            this._appendHeaderRecord(process.name(), this._processHeaderRecord);
297            var objectNames = process.sortedObjectNames();
298            for (var objectNameIndex = 0; objectNameIndex < objectNames.length; ++objectNameIndex) {
299                this._appendHeaderRecord(WebInspector.UIString("Object %s", objectNames[objectNameIndex]), this._threadHeaderRecord);
300                var objects = process.objectsByName(objectNames[objectNameIndex]);
301                for (var objectIndex = 0; objectIndex < objects.length; ++objectIndex)
302                    this._appendRecord(objects[objectIndex], 0);
303                ++this._currentLevel;
304            }
305            var threads = process.sortedThreads();
306            for (var threadIndex = 0; threadIndex < threads.length; ++threadIndex) {
307                this._appendHeaderRecord(threads[threadIndex].name(), this._threadHeaderRecord);
308                var events = threads[threadIndex].events();
309                for (var eventIndex = 0; eventIndex < events.length; ++eventIndex) {
310                    var event = events[eventIndex];
311                    if (event.duration)
312                        this._appendRecord(event, event.level);
313                }
314                this._currentLevel += threads[threadIndex].maxStackDepth();
315            }
316            ++this._currentLevel;
317        }
318        return this._timelineData;
319    },
320
321    /**
322     * @return {number}
323     */
324    minimumBoundary: function()
325    {
326        return this._minimumBoundary;
327    },
328
329    /**
330     * @return {number}
331     */
332    totalTime: function()
333    {
334        return this._timeSpan;
335    },
336
337    /**
338     * @return {number}
339     */
340    maxStackDepth: function()
341    {
342        return this._currentLevel;
343    },
344
345    /**
346     * @param {number} entryIndex
347     * @return {?Array.<!{title: string, text: string}>}
348     */
349    prepareHighlightedEntryInfo: function(entryIndex)
350    {
351        return null;
352    },
353
354    /**
355     * @param {number} entryIndex
356     * @return {boolean}
357     */
358    canJumpToEntry: function(entryIndex)
359    {
360        var record = this._records[entryIndex];
361        return record.phase === WebInspector.TracingModel.Phase.SnapshotObject;
362    },
363
364    /**
365     * @param {number} entryIndex
366     * @return {string}
367     */
368    entryColor: function(entryIndex)
369    {
370        var record = this._records[entryIndex];
371        if (record.phase === WebInspector.TracingModel.Phase.SnapshotObject)
372            return "rgb(20, 150, 20)";
373        if (record === this._processHeaderRecord)
374            return "#555";
375        if (record === this._threadHeaderRecord)
376            return "#777";
377        return this._palette.colorForString(record.name);
378    },
379
380    /**
381     * @param {number} entryIndex
382     * @param {!CanvasRenderingContext2D} context
383     * @param {?string} text
384     * @param {number} barX
385     * @param {number} barY
386     * @param {number} barWidth
387     * @param {number} barHeight
388     * @param {function(number):number} timeToPosition
389     * @return {boolean}
390     */
391    decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, timeToPosition)
392    {
393        return false;
394    },
395
396    /**
397     * @param {number} entryIndex
398     * @return {boolean}
399     */
400    forceDecoration: function(entryIndex)
401    {
402        return false;
403    },
404
405    /**
406     * @param {number} entryIndex
407     * @return {?{startTime: number, endTime: number}}
408     */
409    highlightTimeRange: function(entryIndex)
410    {
411        var record = this._records[entryIndex];
412        if (!record || this._isHeaderRecord(record))
413            return null;
414        return {
415            startTime: record.startTime,
416            endTime: record.endTime
417        }
418    },
419
420    /**
421     * @return {number}
422     */
423    paddingLeft: function()
424    {
425        return 0;
426    },
427
428    /**
429     * @param {number} entryIndex
430     * @return {string}
431     */
432    textColor: function(entryIndex)
433    {
434        return "white";
435    },
436
437    /**
438     * @param {string} title
439     * @param {!WebInspector.TracingModel.Event} record
440     */
441    _appendHeaderRecord: function(title, record)
442    {
443        var index = this._records.length;
444        this._records.push(record);
445        this._timelineData.entryLevels[index] = this._currentLevel++;
446        this._timelineData.entryTotalTimes[index] = this.totalTime();
447        this._timelineData.entryStartTimes[index] = this._minimumBoundary;
448        this._headerTitles[index] = title;
449    },
450
451    /**
452     * @param {!WebInspector.TracingModel.Event} record
453     * @param {number} level
454     */
455    _appendRecord: function(record, level)
456    {
457        var index = this._records.length;
458        this._records.push(record);
459        this._timelineData.entryLevels[index] = this._currentLevel + level;
460        this._timelineData.entryTotalTimes[index] = record.phase === WebInspector.TracingModel.Phase.SnapshotObject ? NaN : record.duration || 0;
461        this._timelineData.entryStartTimes[index] = record.startTime;
462    },
463
464    /**
465     * @param {!WebInspector.TracingModel.Event} record
466     */
467    _isHeaderRecord: function(record)
468    {
469        return record === this._threadHeaderRecord || record === this._processHeaderRecord;
470    },
471
472    /**
473     * @param {number} index
474     * @return {!WebInspector.TracingModel.Event|undefined}
475     */
476    _recordAt: function(index)
477    {
478        return this._records[index];
479    }
480}
481
482// The below logic is shamelessly stolen from https://code.google.com/p/trace-viewer/source/browse/trunk/trace_viewer/tracing/color_scheme.js
483
484/**
485 * @constructor
486 */
487WebInspector.TraceViewPalette = function()
488{
489    this._palette = WebInspector.TraceViewPalette._paletteBase.map(WebInspector.TraceViewPalette._rgbToString);
490}
491
492WebInspector.TraceViewPalette._paletteBase = [
493    [138, 113, 152],
494    [175, 112, 133],
495    [127, 135, 225],
496    [93, 81, 137],
497    [116, 143, 119],
498    [178, 214, 122],
499    [87, 109, 147],
500    [119, 155, 95],
501    [114, 180, 160],
502    [132, 85, 103],
503    [157, 210, 150],
504    [148, 94, 86],
505    [164, 108, 138],
506    [139, 191, 150],
507    [110, 99, 145],
508    [80, 129, 109],
509    [125, 140, 149],
510    [93, 124, 132],
511    [140, 85, 140],
512    [104, 163, 162],
513    [132, 141, 178],
514    [131, 105, 147],
515    [135, 183, 98],
516    [152, 134, 177],
517    [141, 188, 141],
518    [133, 160, 210],
519    [126, 186, 148],
520    [112, 198, 205],
521    [180, 122, 195],
522    [203, 144, 152]
523];
524
525/**
526 * @param {string} string
527 * @return {number}
528 */
529WebInspector.TraceViewPalette._stringHash = function(string)
530{
531    var hash = 0;
532    for (var i = 0; i < string.length; ++i)
533        hash = (hash + 37 * hash + 11 * string.charCodeAt(i)) % 0xFFFFFFFF;
534    return hash;
535}
536
537/**
538 * @param {!Array.<number>} rgb
539 * @return {string}
540 */
541WebInspector.TraceViewPalette._rgbToString = function(rgb)
542{
543    return "rgb(" + rgb.join(",") + ")";
544}
545
546WebInspector.TraceViewPalette.prototype = {
547    /**
548     * @param {string} string
549     * @return {string}
550     */
551    colorForString: function(string)
552    {
553        var hash = WebInspector.TraceViewPalette._stringHash(string);
554        return this._palette[hash % this._palette.length];
555    }
556};
557