input_latency_side_panel.html revision cef7893435aa41160dd1255c43cb8498279738cc
1<!DOCTYPE html>
2<!--
3Copyright 2014 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/base/statistics.html">
9<link rel="import" href="/tracing/model/event_set.html">
10<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
11<link rel="import" href="/tracing/ui/base/dom_helpers.html">
12<link rel="import" href="/tracing/ui/base/line_chart.html">
13<link rel="import" href="/tracing/ui/side_panel/side_panel.html">
14
15<polymer-element name='tr-ui-e-s-input-latency-side-panel'
16    extends='tr-ui-side-panel'>
17  <template>
18    <style>
19    :host {
20      flex-direction: column;
21      display: flex;
22    }
23    toolbar {
24      flex: 0 0 auto;
25      border-bottom: 1px solid black;
26      display: flex;
27    }
28    result-area {
29      flex: 1 1 auto;
30      display: block;
31      min-height: 0;
32      overflow-y: auto;
33    }
34    </style>
35
36    <toolbar id='toolbar'></toolbar>
37    <result-area id='result_area'></result-area>
38  </template>
39
40  <script>
41  'use strict';
42
43  Polymer({
44    ready: function() {
45      this.rangeOfInterest_ = new tr.b.Range();
46      this.frametimeType_ = tr.model.helpers.IMPL_FRAMETIME_TYPE;
47      this.latencyChart_ = undefined;
48      this.frametimeChart_ = undefined;
49      this.selectedProcessId_ = undefined;
50      this.mouseDownIndex_ = undefined;
51      this.curMouseIndex_ = undefined;
52    },
53
54    get model() {
55      return this.model_;
56    },
57
58    set model(model) {
59      this.model_ = model;
60      if (this.model_) {
61        this.modelHelper_ = this.model_.getOrCreateHelper(
62            tr.model.helpers.ChromeModelHelper);
63      } else {
64        this.modelHelper_ = undefined;
65      }
66
67      this.updateToolbar_();
68      this.updateContents_();
69    },
70
71    get frametimeType() {
72      return this.frametimeType_;
73    },
74
75    set frametimeType(type) {
76      if (this.frametimeType_ === type)
77        return;
78      this.frametimeType_ = type;
79      this.updateContents_();
80    },
81
82    get selectedProcessId() {
83      return this.selectedProcessId_;
84    },
85
86    set selectedProcessId(process) {
87      if (this.selectedProcessId_ === process)
88        return;
89      this.selectedProcessId_ = process;
90      this.updateContents_();
91    },
92
93    set selection(selection) {
94      if (this.latencyChart_ === undefined)
95        return;
96      this.latencyChart_.brushedRange = selection.bounds;
97    },
98
99    // This function is for testing purpose.
100    setBrushedIndices: function(mouseDownIndex, curIndex) {
101      this.mouseDownIndex_ = mouseDownIndex;
102      this.curMouseIndex_ = curIndex;
103      this.updateBrushedRange_();
104    },
105
106    updateBrushedRange_: function() {
107      if (this.latencyChart_ === undefined)
108        return;
109
110      var r = new tr.b.Range();
111      if (this.mouseDownIndex_ === undefined) {
112        this.latencyChart_.brushedRange = r;
113        return;
114      }
115      r = this.latencyChart_.computeBrushRangeFromIndices(
116          this.mouseDownIndex_, this.curMouseIndex_);
117      this.latencyChart_.brushedRange = r;
118
119      // Based on the brushed range, update the selection of LatencyInfo in
120      // the timeline view by sending a selectionChange event.
121      var latencySlices = [];
122      this.model_.getAllThreads().forEach(function(thread) {
123        thread.iterateAllEvents(function(event) {
124          if (event.title.indexOf('InputLatency:') === 0)
125            latencySlices.push(event);
126        });
127      });
128      latencySlices = tr.model.helpers.getSlicesIntersectingRange(
129          r, latencySlices);
130
131      var event = new tr.model.RequestSelectionChangeEvent();
132      event.selection = new tr.model.EventSet(latencySlices);
133      this.latencyChart_.dispatchEvent(event);
134    },
135
136    registerMouseEventForLatencyChart_: function() {
137      this.latencyChart_.addEventListener('item-mousedown', function(e) {
138        this.mouseDownIndex_ = e.index;
139        this.curMouseIndex_ = e.index;
140        this.updateBrushedRange_();
141      }.bind(this));
142
143      this.latencyChart_.addEventListener('item-mousemove', function(e) {
144        if (e.button == undefined)
145          return;
146        this.curMouseIndex_ = e.index;
147        this.updateBrushedRange_();
148      }.bind(this));
149
150      this.latencyChart_.addEventListener('item-mouseup', function(e) {
151        this.curMouseIndex = e.index;
152        this.updateBrushedRange_();
153      }.bind(this));
154    },
155
156    updateToolbar_: function() {
157      var browserProcess = this.modelHelper_.browserProcess;
158      var labels = [];
159
160      if (browserProcess !== undefined) {
161        var label_str = 'Browser: ' + browserProcess.pid;
162        labels.push({label: label_str, value: browserProcess.pid});
163      }
164
165      tr.b.iterItems(this.modelHelper_.rendererHelpers,
166        function(pid, rendererHelper) {
167          var rendererProcess = rendererHelper.process;
168          var label_str = 'Renderer: ' + rendererProcess.userFriendlyName;
169          labels.push({label: label_str, value: rendererProcess.userFriendlyName
170        });
171      }, this);
172
173      if (labels.length === 0)
174        return;
175
176      this.selectedProcessId_ = labels[0].value;
177      var toolbarEl = this.$.toolbar;
178      toolbarEl.appendChild(tr.ui.b.createSelector(
179          this, 'frametimeType',
180          'inputLatencySidePanel.frametimeType', this.frametimeType_,
181          [{label: 'Main Thread Frame Times',
182            value: tr.model.helpers.MAIN_FRAMETIME_TYPE},
183           {label: 'Impl Thread Frame Times',
184            value: tr.model.helpers.IMPL_FRAMETIME_TYPE}
185          ]));
186      toolbarEl.appendChild(tr.ui.b.createSelector(
187          this, 'selectedProcessId',
188          'inputLatencySidePanel.selectedProcessId',
189          this.selectedProcessId_,
190          labels));
191    },
192
193    get currentRangeOfInterest() {
194      if (this.rangeOfInterest_.isEmpty)
195        return this.model_.bounds;
196      else
197        return this.rangeOfInterest_;
198    },
199
200    createLatencyLineChart: function(data, title) {
201      var chart = new tr.ui.b.LineChart();
202      var width = 600;
203      if (document.body.clientWidth != undefined)
204        width = document.body.clientWidth * 0.5;
205      chart.setSize({width: width, height: chart.height});
206      chart.chartTitle = title;
207      chart.data = data;
208      return chart;
209    },
210
211    updateContents_: function() {
212      var resultArea = this.$.result_area;
213      this.latencyChart_ = undefined;
214      this.frametimeChart_ = undefined;
215      resultArea.textContent = '';
216
217      if (this.modelHelper_ === undefined)
218        return;
219
220      var rangeOfInterest = this.currentRangeOfInterest;
221
222      var chromeProcess;
223      if (this.modelHelper_.rendererHelpers[this.selectedProcessId_])
224        chromeProcess = this.modelHelper_.rendererHelpers[
225          this.selectedProcessId_
226        ];
227      else
228        chromeProcess = this.modelHelper_.browserHelper;
229
230      var frameEvents = chromeProcess.getFrameEventsInRange(
231          this.frametimeType, rangeOfInterest);
232
233      var frametimeData = tr.model.helpers.getFrametimeDataFromEvents(
234          frameEvents);
235      var averageFrametime = tr.b.Statistics.mean(frametimeData, function(d) {
236        return d.frametime;
237      });
238
239      var latencyEvents = this.modelHelper_.browserHelper.
240        getLatencyEventsInRange(
241          rangeOfInterest);
242
243      var latencyData = [];
244      latencyEvents.forEach(function(event) {
245        if (event.inputLatency === undefined)
246          return;
247        latencyData.push({
248          x: event.start,
249          latency: event.inputLatency / 1000
250        });
251      });
252
253      var averageLatency = tr.b.Statistics.mean(latencyData, function(d) {
254        return d.latency;
255      });
256
257      // Create summary.
258      var latencySummaryText = document.createElement('div');
259      latencySummaryText.appendChild(tr.ui.b.createSpan({
260        textContent: 'Average Latency ' + averageLatency + ' ms',
261        bold: true}));
262      resultArea.appendChild(latencySummaryText);
263
264      var frametimeSummaryText = document.createElement('div');
265      frametimeSummaryText.appendChild(tr.ui.b.createSpan({
266        textContent: 'Average Frame Time ' + averageFrametime + ' ms',
267        bold: true}));
268      resultArea.appendChild(frametimeSummaryText);
269
270      if (latencyData.length !== 0) {
271        this.latencyChart_ = this.createLatencyLineChart(
272            latencyData, 'Latency Over Time');
273        this.registerMouseEventForLatencyChart_();
274        resultArea.appendChild(this.latencyChart_);
275      }
276
277      if (frametimeData.length != 0) {
278        this.frametimeChart_ = this.createLatencyLineChart(
279            frametimeData, 'Frame Times');
280        this.frametimeChart_.style.display = 'block';
281        resultArea.appendChild(this.frametimeChart_);
282      }
283    },
284
285    get rangeOfInterest() {
286      return this.rangeOfInterest_;
287    },
288
289    set rangeOfInterest(rangeOfInterest) {
290      this.rangeOfInterest_ = rangeOfInterest;
291      this.updateContents_();
292    },
293
294    supportsModel: function(m) {
295      if (m == undefined) {
296        return {
297          supported: false,
298          reason: 'Unknown tracing model'
299        };
300      }
301
302      if (!tr.model.helpers.ChromeModelHelper.supportsModel(m)) {
303        return {
304          supported: false,
305          reason: 'No Chrome browser or renderer process found'
306        };
307      }
308
309      var modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
310      if (modelHelper.browserHelper &&
311        modelHelper.browserHelper.hasLatencyEvents) {
312          return {
313            supported: true
314          };
315      }
316
317      return {
318        supported: false,
319        reason: 'No InputLatency events trace. Consider enabling ' +
320            'benchmark" and "input" category when recording the trace'
321      };
322    },
323
324    get textLabel() {
325      return 'Input Latency';
326    }
327  });
328  </script>
329</polymer-element>
330