Home | History | Annotate | Download | only in timeline
      1 /*
      2  * Copyright (C) 2014 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @implements {WebInspector.FlameChartDataProvider}
     34  * @param {!WebInspector.TracingTimelineModel} model
     35  * @param {!WebInspector.TimelineFrameModelBase} frameModel
     36  */
     37 WebInspector.TimelineFlameChartDataProvider = function(model, frameModel)
     38 {
     39     WebInspector.FlameChartDataProvider.call(this);
     40     this.reset();
     41     this._model = model;
     42     this._frameModel = frameModel;
     43     this._font = "12px " + WebInspector.fontFamily();
     44     this._linkifier = new WebInspector.Linkifier();
     45     this._captureStacksSetting = WebInspector.settings.createSetting("timelineCaptureStacks", true);
     46     this._filters = [];
     47     this.addFilter(WebInspector.TracingTimelineUIUtils.hiddenEventsFilter());
     48     this.addFilter(new WebInspector.TracingTimelineModel.ExclusiveEventNameFilter([WebInspector.TracingTimelineModel.RecordType.Program]));
     49 }
     50 
     51 WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs = 0.01;
     52 WebInspector.TimelineFlameChartDataProvider.JSFrameCoalesceThresholdMs = 1.1;
     53 
     54 /**
     55  * @return {!WebInspector.FlameChart.ColorGenerator}
     56  */
     57 WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator = function()
     58 {
     59     if (!WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator._consoleEventsColorGenerator) {
     60         var hueSpace = { min: 30, max: 55, count: 5 };
     61         var satSpace = { min: 70, max: 100, count: 6 };
     62         var colorGenerator = new WebInspector.FlameChart.ColorGenerator(hueSpace, satSpace, 50, 0.7);
     63         WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator._consoleEventsColorGenerator = colorGenerator;
     64     }
     65     return WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator._consoleEventsColorGenerator;
     66 }
     67 
     68 WebInspector.TimelineFlameChartDataProvider.prototype = {
     69     /**
     70      * @return {number}
     71      */
     72     barHeight: function()
     73     {
     74         return 20;
     75     },
     76 
     77     /**
     78      * @return {number}
     79      */
     80     textBaseline: function()
     81     {
     82         return 6;
     83     },
     84 
     85     /**
     86      * @return {number}
     87      */
     88     textPadding: function()
     89     {
     90         return 5;
     91     },
     92 
     93     /**
     94      * @param {number} entryIndex
     95      * @return {string}
     96      */
     97     entryFont: function(entryIndex)
     98     {
     99         return this._font;
    100     },
    101 
    102     /**
    103      * @param {number} entryIndex
    104      * @return {?string}
    105      */
    106     entryTitle: function(entryIndex)
    107     {
    108         var event = this._entryEvents[entryIndex];
    109         if (event) {
    110             if (event.phase === WebInspector.TracingModel.Phase.AsyncStepInto || event.phase === WebInspector.TracingModel.Phase.AsyncStepPast)
    111                 return event.name + ":" + event.args["step"];
    112 
    113             var name = WebInspector.TracingTimelineUIUtils.eventStyle(event).title;
    114             // TODO(yurys): support event dividers
    115             var details = WebInspector.TracingTimelineUIUtils.buildDetailsNodeForTraceEvent(event, this._linkifier);
    116             if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame && details)
    117                 return details.textContent;
    118             return details ? WebInspector.UIString("%s (%s)", name, details.textContent) : name;
    119         }
    120         var title = this._entryIndexToTitle[entryIndex];
    121         if (!title) {
    122             title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex);
    123             console.error(title);
    124         }
    125         return title;
    126     },
    127 
    128     /**
    129      * @param {number} startTime
    130      * @param {number} endTime
    131      * @return {?Array.<number>}
    132      */
    133     dividerOffsets: function(startTime, endTime)
    134     {
    135         return null;
    136     },
    137 
    138     /**
    139      * @override
    140      * @param {number} index
    141      * @return {string}
    142      */
    143     markerColor: function(index)
    144     {
    145         var event = this._markerEvents[index];
    146         return WebInspector.TracingTimelineUIUtils.markerEventColor(event);
    147     },
    148 
    149     /**
    150      * @override
    151      * @param {number} index
    152      * @return {string}
    153      */
    154     markerTitle: function(index)
    155     {
    156         var event = this._markerEvents[index];
    157         return WebInspector.TracingTimelineUIUtils.eventTitle(event, this._model);
    158     },
    159 
    160     reset: function()
    161     {
    162         this._timelineData = null;
    163         /** @type {!Array.<!WebInspector.TracingModel.Event>} */
    164         this._entryEvents = [];
    165         this._entryIndexToTitle = {};
    166         this._markerEvents = [];
    167         this._entryIndexToFrame = {};
    168         this._asyncColorByCategory = {};
    169     },
    170 
    171     /**
    172      * @return {!WebInspector.FlameChart.TimelineData}
    173      */
    174     timelineData: function()
    175     {
    176         if (this._timelineData)
    177             return this._timelineData;
    178 
    179         this._timelineData = new WebInspector.FlameChart.TimelineData([], [], []);
    180 
    181         this._minimumBoundary = this._model.minimumRecordTime();
    182         this._timeSpan = this._model.isEmpty() ?  1000 : this._model.maximumRecordTime() - this._minimumBoundary;
    183         this._currentLevel = 0;
    184         this._appendFrameBars(this._frameModel.frames());
    185         this._appendThreadTimelineData(WebInspector.UIString("Main Thread"), this._model.mainThreadEvents(), this._model.mainThreadAsyncEvents());
    186         var threads = this._model.virtualThreads();
    187         for (var i = 0; i < threads.length; i++)
    188             this._appendThreadTimelineData(threads[i].name, threads[i].events, threads[i].asyncEvents);
    189         return this._timelineData;
    190     },
    191 
    192     /**
    193      * @param {string} threadTitle
    194      * @param {!Array.<!WebInspector.TracingModel.Event>} syncEvents
    195      * @param {!Array.<!Array.<!WebInspector.TracingModel.Event>>} asyncEvents
    196      */
    197     _appendThreadTimelineData: function(threadTitle, syncEvents, asyncEvents)
    198     {
    199         var levelCount = this._appendAsyncEvents(threadTitle, asyncEvents);
    200         if (Runtime.experiments.isEnabled("timelineJSCPUProfile")) {
    201             if (this._captureStacksSetting.get()) {
    202                 var jsFrameEvents = this._generateJSFrameEvents(syncEvents);
    203                 syncEvents = jsFrameEvents.mergeOrdered(syncEvents, WebInspector.TracingModel.Event.orderedCompareStartTime);
    204             }
    205         }
    206         levelCount += this._appendSyncEvents(levelCount ? null : threadTitle, syncEvents);
    207         if (levelCount)
    208             ++this._currentLevel;
    209     },
    210 
    211     /**
    212      * @param {?string} headerName
    213      * @param {!Array.<!WebInspector.TracingModel.Event>} events
    214      * @return {boolean}
    215      */
    216     _appendSyncEvents: function(headerName, events)
    217     {
    218         var openEvents = [];
    219         var headerAppended = false;
    220 
    221         var maxStackDepth = 0;
    222         for (var i = 0; i < events.length; ++i) {
    223             var e = events[i];
    224             if (WebInspector.TracingTimelineUIUtils.isMarkerEvent(e)) {
    225                 this._markerEvents.push(e);
    226                 this._timelineData.markerTimestamps.push(e.startTime);
    227             }
    228             if (!e.endTime && e.phase !== WebInspector.TracingModel.Phase.Instant)
    229                 continue;
    230             if (!this._isVisible(e))
    231                 continue;
    232             while (openEvents.length && openEvents.peekLast().endTime <= e.startTime)
    233                 openEvents.pop();
    234             if (!headerAppended && headerName) {
    235                 this._appendHeaderRecord(headerName, this._currentLevel++);
    236                 headerAppended = true;
    237             }
    238             this._appendEvent(e, this._currentLevel + openEvents.length);
    239             maxStackDepth = Math.max(maxStackDepth, openEvents.length + 1);
    240             if (e.endTime)
    241                 openEvents.push(e);
    242         }
    243         this._currentLevel += maxStackDepth;
    244         return !!maxStackDepth;
    245     },
    246 
    247     /**
    248      * @param {string} header
    249      * @param {!Array.<!Array.<!WebInspector.TracingModel.Event>>} eventSteps
    250      */
    251     _appendAsyncEvents: function(header, eventSteps)
    252     {
    253         var lastUsedTimeByLevel = [];
    254         var headerAppended = false;
    255 
    256         var maxStackDepth = 0;
    257         for (var i = 0; i < eventSteps.length; ++i) {
    258             var e = eventSteps[i][0];
    259             if (!this._isVisible(e))
    260                 continue;
    261             if (!headerAppended && header) {
    262                 this._appendHeaderRecord(header, this._currentLevel++);
    263                 headerAppended = true;
    264             }
    265             var level;
    266             for (level = 0; level < lastUsedTimeByLevel.length && lastUsedTimeByLevel[level] > e.startTime; ++level) {}
    267             this._appendAsyncEventSteps(eventSteps[i], this._currentLevel + level);
    268             var lastStep = eventSteps[i].peekLast();
    269             lastUsedTimeByLevel[level] = lastStep.phase === WebInspector.TracingModel.Phase.AsyncEnd ? lastStep.startTime : Infinity;
    270         }
    271         this._currentLevel += lastUsedTimeByLevel.length;
    272         return lastUsedTimeByLevel.length;
    273     },
    274 
    275     /**
    276      * @param {!Array.<!WebInspector.TimelineFrame>} frames
    277      */
    278     _appendFrameBars: function(frames)
    279     {
    280         this._frameBarsLevel = this._currentLevel++;
    281         for (var i = 0; i < frames.length; ++i)
    282             this._appendFrame(frames[i]);
    283     },
    284 
    285     /**
    286      * @param {!Array.<!WebInspector.TracingModel.Event>} events
    287      * @return {!Array.<!WebInspector.TracingModel.Event>}
    288      */
    289     _generateJSFrameEvents: function(events)
    290     {
    291         function equalFrames(frame1, frame2)
    292         {
    293             return frame1.scriptId === frame2.scriptId && frame1.functionName === frame2.functionName;
    294         }
    295 
    296         function eventEndTime(e)
    297         {
    298             return e.endTime || e.startTime;
    299         }
    300 
    301         function isJSInvocationEvent(e)
    302         {
    303             switch (e.name) {
    304             case WebInspector.TracingTimelineModel.RecordType.FunctionCall:
    305             case WebInspector.TracingTimelineModel.RecordType.EvaluateScript:
    306                 return true;
    307             }
    308             return false;
    309         }
    310 
    311         var jsFrameEvents = [];
    312         var jsFramesStack = [];
    313         var coalesceThresholdMs = WebInspector.TimelineFlameChartDataProvider.JSFrameCoalesceThresholdMs;
    314 
    315         function onStartEvent(e)
    316         {
    317             extractStackTrace(e);
    318         }
    319 
    320         function onInstantEvent(e, top)
    321         {
    322             if (e.name === WebInspector.TracingTimelineModel.RecordType.JSSample && top && !isJSInvocationEvent(top))
    323                 return;
    324             extractStackTrace(e);
    325         }
    326 
    327         function onEndEvent(e)
    328         {
    329             if (isJSInvocationEvent(e))
    330                 jsFramesStack.length = 0;
    331         }
    332 
    333         function extractStackTrace(e)
    334         {
    335             if (!e.stackTrace)
    336                 return;
    337             while (jsFramesStack.length && eventEndTime(jsFramesStack.peekLast()) + coalesceThresholdMs <= e.startTime)
    338                 jsFramesStack.pop();
    339             var endTime = eventEndTime(e);
    340             var numFrames = e.stackTrace.length;
    341             var minFrames = Math.min(numFrames, jsFramesStack.length);
    342             var j;
    343             for (j = 0; j < minFrames; ++j) {
    344                 var newFrame = e.stackTrace[numFrames - 1 - j];
    345                 var oldFrame = jsFramesStack[j].args["data"];
    346                 if (!equalFrames(newFrame, oldFrame))
    347                     break;
    348                 jsFramesStack[j].setEndTime(Math.max(jsFramesStack[j].endTime, endTime));
    349             }
    350             jsFramesStack.length = j;
    351             for (; j < numFrames; ++j) {
    352                 var frame = e.stackTrace[numFrames - 1 - j];
    353                 var jsFrameEvent = new WebInspector.TracingModel.Event(WebInspector.TracingModel.DevToolsMetadataEventCategory, WebInspector.TracingTimelineModel.RecordType.JSFrame,
    354                     WebInspector.TracingModel.Phase.Complete, e.startTime, e.thread);
    355                 jsFrameEvent.addArgs({ data: frame });
    356                 jsFrameEvent.setEndTime(endTime);
    357                 jsFramesStack.push(jsFrameEvent);
    358                 jsFrameEvents.push(jsFrameEvent);
    359             }
    360         }
    361 
    362         var stack = [];
    363         for (var i = 0; i < events.length; ++i) {
    364             var e = events[i];
    365             var top = stack.peekLast();
    366             if (top && top.endTime <= e.startTime)
    367                 onEndEvent(stack.pop());
    368             if (e.duration) {
    369                 onStartEvent(e);
    370                 stack.push(e);
    371             } else {
    372                 onInstantEvent(e, stack.peekLast());
    373             }
    374         }
    375         while (stack.length)
    376             onEndEvent(stack.pop());
    377 
    378         return jsFrameEvents;
    379     },
    380 
    381     /**
    382      * @param {!WebInspector.TracingTimelineModel.Filter} filter
    383      */
    384     addFilter: function(filter)
    385     {
    386         this._filters.push(filter);
    387     },
    388 
    389     /**
    390      * @param {!WebInspector.TracingModel.Event} event
    391      * @return {boolean}
    392      */
    393     _isVisible: function(event)
    394     {
    395         return this._filters.every(function (filter) { return filter.accept(event); });
    396     },
    397 
    398     /**
    399      * @return {number}
    400      */
    401     minimumBoundary: function()
    402     {
    403         return this._minimumBoundary;
    404     },
    405 
    406     /**
    407      * @return {number}
    408      */
    409     totalTime: function()
    410     {
    411         return this._timeSpan;
    412     },
    413 
    414     /**
    415      * @return {number}
    416      */
    417     maxStackDepth: function()
    418     {
    419         return this._currentLevel;
    420     },
    421 
    422     /**
    423      * @param {number} entryIndex
    424      * @return {?Array.<!{title: string, text: string}>}
    425      */
    426     prepareHighlightedEntryInfo: function(entryIndex)
    427     {
    428         return null;
    429     },
    430 
    431     /**
    432      * @param {number} entryIndex
    433      * @return {boolean}
    434      */
    435     canJumpToEntry: function(entryIndex)
    436     {
    437         return false;
    438     },
    439 
    440     /**
    441      * @param {number} entryIndex
    442      * @return {string}
    443      */
    444     entryColor: function(entryIndex)
    445     {
    446         var event = this._entryEvents[entryIndex];
    447         if (!event)
    448             return this._entryIndexToFrame[entryIndex] ? "white" : "#555";
    449         if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame)
    450             return this._timelineData.entryLevels[entryIndex] % 2 ? "#efb320" : "#fcc02d";
    451         var category = WebInspector.TracingTimelineUIUtils.eventStyle(event).category;
    452         if (WebInspector.TracingModel.isAsyncPhase(event.phase)) {
    453             if (event.category === WebInspector.TracingModel.ConsoleEventCategory)
    454                 return WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator().colorForID(event.name);
    455             var color = this._asyncColorByCategory[category.name];
    456             if (color)
    457                 return color;
    458             var parsedColor = WebInspector.Color.parse(category.fillColorStop1);
    459             color = parsedColor.setAlpha(0.7).toString(WebInspector.Color.Format.RGBA) || "";
    460             this._asyncColorByCategory[category.name] = color;
    461             return color;
    462         }
    463         return category.fillColorStop1;
    464     },
    465 
    466     /**
    467      * @param {number} entryIndex
    468      * @param {!CanvasRenderingContext2D} context
    469      * @param {?string} text
    470      * @param {number} barX
    471      * @param {number} barY
    472      * @param {number} barWidth
    473      * @param {number} barHeight
    474      * @param {function(number):number} offsetToPosition
    475      * @return {boolean}
    476      */
    477     decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
    478     {
    479         var frame = this._entryIndexToFrame[entryIndex];
    480         if (frame) {
    481             context.save();
    482 
    483             context.translate(0.5, 0.5);
    484 
    485             context.beginPath();
    486             context.moveTo(barX, barY);
    487             context.lineTo(barX, context.canvas.height);
    488             context.strokeStyle = "rgba(100, 100, 100, 0.4)";
    489             context.setLineDash([5]);
    490             context.stroke();
    491             context.setLineDash([]);
    492 
    493 
    494             var padding = 4 * window.devicePixelRatio;
    495             barX += padding;
    496             barWidth -= 2 * padding;
    497             barY += padding;
    498             barHeight -= 2 * padding;
    499 
    500             var cornerRadis = 3;
    501             var radiusY = cornerRadis;
    502             var radiusX = Math.min(cornerRadis, barWidth / 2);
    503 
    504             context.beginPath();
    505             context.moveTo(barX + radiusX, barY);
    506             context.lineTo(barX + barWidth - radiusX, barY);
    507             context.quadraticCurveTo(barX + barWidth, barY, barX + barWidth, barY + radiusY);
    508             context.lineTo(barX + barWidth, barY + barHeight - radiusY);
    509             context.quadraticCurveTo(barX + barWidth, barY + barHeight, barX + barWidth - radiusX, barY + barHeight);
    510             context.lineTo(barX + radiusX, barY + barHeight);
    511             context.quadraticCurveTo(barX, barY + barHeight, barX, barY + barHeight - radiusY);
    512             context.lineTo(barX, barY + radiusY);
    513             context.quadraticCurveTo(barX, barY, barX + radiusX, barY);
    514             context.closePath();
    515 
    516             context.fillStyle = "rgba(200, 200, 200, 0.8)";
    517             context.fill();
    518             context.strokeStyle = "rgba(150, 150, 150, 0.8)";
    519             context.stroke();
    520 
    521             var frameDurationText = Number.millisToString(frame.duration, true);
    522             var textWidth = context.measureText(frameDurationText).width;
    523             if (barWidth > textWidth) {
    524                 context.fillStyle = "#555";
    525                 context.fillText(frameDurationText, barX + ((barWidth - textWidth) >> 1), barY + barHeight - 2);
    526             }
    527             context.restore();
    528             return true;
    529         }
    530         if (barWidth < 5)
    531             return false;
    532 
    533         // Paint text using white color on dark background.
    534         if (text) {
    535             context.save();
    536             context.fillStyle = "white";
    537             context.shadowColor = "rgba(0, 0, 0, 0.1)";
    538             context.shadowOffsetX = 1;
    539             context.shadowOffsetY = 1;
    540             context.font = this._font;
    541             context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
    542             context.restore();
    543         }
    544 
    545         var event = this._entryEvents[entryIndex];
    546         if (event && event.warning) {
    547             context.save();
    548 
    549             context.rect(barX, barY, barWidth, this.barHeight());
    550             context.clip();
    551 
    552             context.beginPath();
    553             context.fillStyle = "red";
    554             context.moveTo(barX + barWidth - 15, barY + 1);
    555             context.lineTo(barX + barWidth - 1, barY + 1);
    556             context.lineTo(barX + barWidth - 1, barY + 15);
    557             context.fill();
    558 
    559             context.restore();
    560         }
    561 
    562         return true;
    563     },
    564 
    565     /**
    566      * @param {number} entryIndex
    567      * @return {boolean}
    568      */
    569     forceDecoration: function(entryIndex)
    570     {
    571         var event = this._entryEvents[entryIndex];
    572         if (!event)
    573             return !!this._entryIndexToFrame[entryIndex];
    574         return !!event.warning;
    575     },
    576 
    577    /**
    578      * @param {number} entryIndex
    579      * @return {?{startTime: number, endTime: number}}
    580      */
    581     highlightTimeRange: function(entryIndex)
    582     {
    583         var startTime = this._timelineData.entryStartTimes[entryIndex];
    584         if (!startTime)
    585             return null;
    586         return {
    587             startTime: startTime,
    588             endTime: startTime + this._timelineData.entryTotalTimes[entryIndex]
    589         }
    590     },
    591 
    592     /**
    593      * @return {number}
    594      */
    595     paddingLeft: function()
    596     {
    597         return 0;
    598     },
    599 
    600     /**
    601      * @param {number} entryIndex
    602      * @return {string}
    603      */
    604     textColor: function(entryIndex)
    605     {
    606         return "white";
    607     },
    608 
    609     /**
    610      * @param {string} title
    611      * @param {number} level
    612      */
    613     _appendHeaderRecord: function(title, level)
    614     {
    615         var index = this._entryEvents.length;
    616         this._entryIndexToTitle[index] = title;
    617         this._entryEvents.push(null);
    618         this._timelineData.entryLevels[index] = level;
    619         this._timelineData.entryTotalTimes[index] = this._timeSpan;
    620         this._timelineData.entryStartTimes[index] = this._minimumBoundary;
    621     },
    622 
    623     /**
    624      * @param {!WebInspector.TracingModel.Event} event
    625      * @param {number} level
    626      */
    627     _appendEvent: function(event, level)
    628     {
    629         var index = this._entryEvents.length;
    630         this._entryEvents.push(event);
    631         this._timelineData.entryLevels[index] = level;
    632         this._timelineData.entryTotalTimes[index] = event.duration || WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs;
    633         this._timelineData.entryStartTimes[index] = event.startTime;
    634     },
    635 
    636     /**
    637      * @param {!Array.<!WebInspector.TracingModel.Event>} steps
    638      * @param {number} level
    639      */
    640     _appendAsyncEventSteps: function(steps, level)
    641     {
    642         // If we have past steps, put the end event for each range rather than start one.
    643         var eventOffset = steps[1].phase === WebInspector.TracingModel.Phase.AsyncStepPast ? 1 : 0;
    644         for (var i = 0; i < steps.length - 1; ++i) {
    645             var index = this._entryEvents.length;
    646             this._entryEvents.push(steps[i + eventOffset]);
    647             var startTime = steps[i].startTime;
    648             this._timelineData.entryLevels[index] = level;
    649             this._timelineData.entryTotalTimes[index] = steps[i + 1].startTime - startTime;
    650             this._timelineData.entryStartTimes[index] = startTime;
    651         }
    652     },
    653 
    654     /**
    655      * @param {!WebInspector.TimelineFrame} frame
    656      */
    657     _appendFrame: function(frame)
    658     {
    659         var index = this._entryEvents.length;
    660         this._entryEvents.push(null);
    661         this._entryIndexToFrame[index] = frame;
    662         this._entryIndexToTitle[index] = Number.millisToString(frame.duration, true);
    663         this._timelineData.entryLevels[index] = this._frameBarsLevel;
    664         this._timelineData.entryTotalTimes[index] = frame.duration;
    665         this._timelineData.entryStartTimes[index] = frame.startTime;
    666     },
    667 
    668     /**
    669      * @param {number} entryIndex
    670      * @return {?WebInspector.TimelineSelection}
    671      */
    672     createSelection: function(entryIndex)
    673     {
    674         var event = this._entryEvents[entryIndex];
    675         if (event) {
    676             this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
    677             return this._lastSelection.timelineSelection;
    678         }
    679         var frame = this._entryIndexToFrame[entryIndex];
    680         if (frame) {
    681             this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), entryIndex);
    682             return this._lastSelection.timelineSelection;
    683         }
    684         return null;
    685     },
    686 
    687     /**
    688      * @param {?WebInspector.TimelineSelection} selection
    689      * @return {number}
    690      */
    691     entryIndexForSelection: function(selection)
    692     {
    693         if (!selection)
    694             return -1;
    695 
    696         if (this._lastSelection && this._lastSelection.timelineSelection.object() === selection.object())
    697             return this._lastSelection.entryIndex;
    698         switch  (selection.type()) {
    699         case WebInspector.TimelineSelection.Type.TraceEvent:
    700             var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object());
    701             var entryEvents = this._entryEvents;
    702             for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) {
    703                 if (entryEvents[entryIndex] === event) {
    704                     this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
    705                     return entryIndex;
    706                 }
    707             }
    708             break;
    709         case WebInspector.TimelineSelection.Type.Frame:
    710             var frame = /** @type {!WebInspector.TimelineFrame} */ (selection.object());
    711             for (var frameIndex in this._entryIndexToFrame) {
    712                 if (this._entryIndexToFrame[frameIndex] === frame) {
    713                     this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), Number(frameIndex));
    714                     return Number(frameIndex);
    715                 }
    716             }
    717             break;
    718         }
    719         return -1;
    720     }
    721 }
    722 
    723 /**
    724  * @constructor
    725  * @extends {WebInspector.VBox}
    726  * @implements {WebInspector.TimelineModeView}
    727  * @implements {WebInspector.FlameChartDelegate}
    728  * @param {!WebInspector.TimelineModeViewDelegate} delegate
    729  * @param {!WebInspector.TracingTimelineModel} tracingModel
    730  * @param {!WebInspector.TimelineFrameModelBase} frameModel
    731  */
    732 WebInspector.TimelineFlameChart = function(delegate, tracingModel, frameModel)
    733 {
    734     WebInspector.VBox.call(this);
    735     this.element.classList.add("timeline-flamechart");
    736     this.registerRequiredCSS("flameChart.css");
    737     this._delegate = delegate;
    738     this._model = tracingModel;
    739     this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(tracingModel, frameModel)
    740     this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true);
    741     this._mainView.show(this.element);
    742     this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
    743     this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
    744 }
    745 
    746 WebInspector.TimelineFlameChart.prototype = {
    747     dispose: function()
    748     {
    749         this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
    750         this._mainView.removeEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
    751     },
    752 
    753     /**
    754      * @param {number} windowStartTime
    755      * @param {number} windowEndTime
    756      */
    757     requestWindowTimes: function(windowStartTime, windowEndTime)
    758     {
    759         this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
    760     },
    761 
    762     /**
    763      * @param {?RegExp} textFilter
    764      */
    765     refreshRecords: function(textFilter)
    766     {
    767         this._dataProvider.reset();
    768         this._mainView.scheduleUpdate();
    769     },
    770 
    771     wasShown: function()
    772     {
    773         this._mainView.scheduleUpdate();
    774     },
    775 
    776     /**
    777      * @return {!WebInspector.View}
    778      */
    779     view: function()
    780     {
    781         return this;
    782     },
    783 
    784     reset: function()
    785     {
    786         this._automaticallySizeWindow = true;
    787         this._dataProvider.reset();
    788         this._mainView.reset();
    789         this._mainView.setWindowTimes(0, Infinity);
    790     },
    791 
    792     _onRecordingStarted: function()
    793     {
    794         this._automaticallySizeWindow = true;
    795         this._mainView.reset();
    796     },
    797 
    798     /**
    799      * @param {!WebInspector.TimelineModel.Record} record
    800      */
    801     addRecord: function(record)
    802     {
    803         this._dataProvider.reset();
    804         if (this._automaticallySizeWindow) {
    805             var minimumRecordTime = this._model.minimumRecordTime();
    806             if (record.startTime() > (minimumRecordTime + 1000)) {
    807                 this._automaticallySizeWindow = false;
    808                 this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000);
    809             }
    810             this._mainView.scheduleUpdate();
    811         } else {
    812             if (!this._pendingUpdateTimer)
    813                 this._pendingUpdateTimer = window.setTimeout(this._updateOnAddRecord.bind(this), 300);
    814         }
    815     },
    816 
    817     _updateOnAddRecord: function()
    818     {
    819         delete this._pendingUpdateTimer;
    820         this._mainView.scheduleUpdate();
    821     },
    822 
    823     /**
    824      * @param {number} startTime
    825      * @param {number} endTime
    826      */
    827     setWindowTimes: function(startTime, endTime)
    828     {
    829         this._mainView.setWindowTimes(startTime, endTime);
    830         this._delegate.select(null);
    831     },
    832 
    833     /**
    834      * @param {number} width
    835      */
    836     setSidebarSize: function(width)
    837     {
    838     },
    839 
    840     /**
    841      * @param {?WebInspector.TimelineModel.Record} record
    842      * @param {string=} regex
    843      * @param {boolean=} selectRecord
    844      */
    845     highlightSearchResult: function(record, regex, selectRecord)
    846     {
    847     },
    848 
    849     /**
    850      * @param {?WebInspector.TimelineSelection} selection
    851      */
    852     setSelection: function(selection)
    853     {
    854         var index = this._dataProvider.entryIndexForSelection(selection);
    855         this._mainView.setSelectedEntry(index);
    856     },
    857 
    858     /**
    859      * @param {!WebInspector.Event} event
    860      */
    861     _onEntrySelected: function(event)
    862     {
    863         var entryIndex = /** @type{number} */ (event.data);
    864         var timelineSelection = this._dataProvider.createSelection(entryIndex);
    865         if (timelineSelection)
    866             this._delegate.select(timelineSelection);
    867     },
    868 
    869     __proto__: WebInspector.VBox.prototype
    870 }
    871 
    872 /**
    873   * @constructor
    874   * @param {!WebInspector.TimelineSelection} selection
    875   * @param {number} entryIndex
    876   */
    877 WebInspector.TimelineFlameChart.Selection = function(selection, entryIndex)
    878 {
    879     this.timelineSelection = selection;
    880     this.entryIndex = entryIndex;
    881 }
    882