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  * @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  */
     16 WebInspector.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 
     29 WebInspector.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  */
    186 WebInspector.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 
    209 WebInspector.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  */
    487 WebInspector.TraceViewPalette = function()
    488 {
    489     this._palette = WebInspector.TraceViewPalette._paletteBase.map(WebInspector.TraceViewPalette._rgbToString);
    490 }
    491 
    492 WebInspector.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  */
    529 WebInspector.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  */
    541 WebInspector.TraceViewPalette._rgbToString = function(rgb)
    542 {
    543     return "rgb(" + rgb.join(",") + ")";
    544 }
    545 
    546 WebInspector.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