1 // Copyright (c) 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.
7 tvcm.require('tracing.analysis.util');
8 tvcm.require('tracing.timeline_view_side_panel');
9 tvcm.require('tvcm.iteration_helpers');
10 tvcm.require('tvcm.statistics');
11 tvcm.require('tvcm.ui.dom_helpers');
12 tvcm.require('tvcm.ui.line_chart');
14 tvcm.requireTemplate('tracing.input_latency_side_panel');
16 tvcm.exportTo('tracing', function() {
18 function createLatencyLineChart(data, title) {
19 var chart = new tvcm.ui.LineChart();
21 if (document.body.clientWidth != undefined)
22 width = document.body.clientWidth * 0.5;
23 chart.setSize({width: width, height: chart.height});
24 chart.chartTitle = title;
29 function getSlicesIntersectingRange(rangeOfInterest, slices) {
30 var slicesInFilterRange = [];
31 for (var i = 0; i < slices.length; i++) {
32 var slice = slices[i];
33 if (rangeOfInterest.intersectsExplicitRange(slice.start, slice.end))
34 slicesInFilterRange.push(slice);
36 return slicesInFilterRange;
39 var BROWSER_PROCESS_NAME = 'CrBrowserMain';
41 function findBrowserProcess(model) {
43 model.getAllProcesses().forEach(function(process) {
44 if (process.findAllThreadsNamed(BROWSER_PROCESS_NAME).length != 0)
45 browserProcess = process;
47 return browserProcess;
50 var MAIN_RENDERING_STATS =
51 'BenchmarkInstrumentation::MainThreadRenderingStats';
52 var IMPL_RENDERING_STATS =
53 'BenchmarkInstrumentation::ImplThreadRenderingStats';
55 var MAIN_FRAMETIME_TYPE = 'main_frametime_type';
56 var IMPL_FRAMETIME_TYPE = 'impl_frametime_type';
58 function getFrametimeData(model, frametimeType, rangeOfInterest) {
59 var mainRenderingSlices = [];
60 var implRenderingSlices = [];
61 var browserProcess = findBrowserProcess(model);
62 if (browserProcess != undefined) {
63 browserProcess.iterateAllEvents(function(event) {
64 if (event.title === MAIN_RENDERING_STATS)
65 mainRenderingSlices.push(event);
66 if (event.title === IMPL_RENDERING_STATS)
67 implRenderingSlices.push(event);
71 var renderingSlices = [];
72 if (frametimeType === MAIN_FRAMETIME_TYPE) {
73 renderingSlices = getSlicesIntersectingRange(rangeOfInterest,
75 } else if (frametimeType === IMPL_FRAMETIME_TYPE) {
76 renderingSlices = getSlicesIntersectingRange(rangeOfInterest,
80 var frametimeData = [];
81 renderingSlices.sort(function(a, b) {return a.start - b.start});
82 for (var i = 1; i < renderingSlices.length; i++) {
83 var diff = renderingSlices[i].start - renderingSlices[i - 1].start;
84 frametimeData.push({'x': renderingSlices[i].start, 'frametime': diff});
89 var UI_COMP_NAME = 'INPUT_EVENT_LATENCY_UI_COMPONENT';
90 var ORIGINAL_COMP_NAME = 'INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT';
91 var BEGIN_COMP_NAME = 'INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT';
92 var END_COMP_NAME = 'INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT';
94 function getLatencyData(model, rangeOfInterest) {
95 var latencySlices = [];
96 model.getAllThreads().forEach(function(thread) {
97 thread.iterateAllEvents(function(event) {
98 if (event.title.indexOf('InputLatency') === 0) {
99 latencySlices.push(event);
104 latencySlices = getSlicesIntersectingRange(rangeOfInterest,
106 var latencyData = [];
108 var averageLatency = 0;
110 // Helper function that computes the input latency for one async slice.
111 function getLatency(event) {
112 if ((!('step' in event.args)) || (!('data' in event.args)))
115 var data = event.args.data;
116 if (!(END_COMP_NAME in data))
119 var endTime = data[END_COMP_NAME].time;
120 if (ORIGINAL_COMP_NAME in data) {
121 latency = endTime - data[ORIGINAL_COMP_NAME].time;
122 } else if (UI_COMP_NAME in data) {
123 latency = endTime - data[UI_COMP_NAME].time;
124 } else if (BEGIN_COMP_NAME in data) {
125 latency = endTime - data[BEGIN_COMP_NAME].time;
127 throw new Error('No valid begin latency component');
129 latencyData.push({'x': event.start, 'latency': latency / 1000.0});
132 latencySlices.forEach(getLatency);
133 latencyData.sort(function(a, b) {return a.x - b.x});
140 var InputLatencySidePanel = tvcm.ui.define('x-input-latency-side-panel',
141 tracing.TimelineViewSidePanel);
142 InputLatencySidePanel.textLabel = 'Input Latency';
143 InputLatencySidePanel.supportsModel = function(m) {
144 if (m == undefined) {
147 reason: 'Unknown tracing model'
151 if (findBrowserProcess(m) === undefined) {
154 reason: 'No browser process found'
158 var hasLatencyInfo = false;
159 m.getAllThreads().forEach(function(thread) {
160 thread.iterateAllEvents(function(event) {
161 if (event.title.indexOf('InputLatency') === 0)
162 hasLatencyInfo = true;
166 if (hasLatencyInfo) {
174 reason: 'No InputLatency events trace. Consider enableing "benchmark" and "input" category when recording the trace' // @suppress longLineCheck
178 InputLatencySidePanel.prototype = {
179 __proto__: tracing.TimelineViewSidePanel.prototype,
181 decorate: function() {
182 tracing.TimelineViewSidePanel.prototype.decorate.call(this);
183 this.classList.add('x-input-latency-side-panel');
184 this.appendChild(tvcm.instantiateTemplate(
185 '#x-input-latency-side-panel-template'));
187 this.rangeOfInterest_ = new tvcm.Range();
188 this.frametimeType_ = MAIN_FRAMETIME_TYPE;
189 this.latencyChart_ = undefined;
190 this.frametimeChart_ = undefined;
192 var toolbarEl = this.querySelector('toolbar');
193 toolbarEl.appendChild(tvcm.ui.createSelector(
194 this, 'frametimeType',
195 'inputLatencySidePanel.frametimeType', this.frametimeType_,
196 [{label: 'Main Thread Frame Times', value: MAIN_FRAMETIME_TYPE},
197 {label: 'Impl Thread Frame Times', value: IMPL_FRAMETIME_TYPE}
207 this.updateContents_();
210 get frametimeType() {
211 return this.frametimeType_;
214 set frametimeType(type) {
215 if (this.frametimeType_ === type)
217 this.frametimeType_ = type;
218 this.updateContents_();
221 updateContents_: function() {
222 var resultArea = this.querySelector('result-area');
223 this.latencyChart_ = undefined;
224 this.frametimeChart_ = undefined;
225 resultArea.textContent = '';
227 if (this.model_ === undefined)
231 if (this.rangeOfInterest_.isEmpty)
232 rangeOfInterest = this.model_.bounds;
234 rangeOfInterest = this.rangeOfInterest_;
237 var frametimeData = getFrametimeData(this.model_, this.frametimeType,
239 var averageFrametime = tvcm.Statistics.mean(frametimeData, function(d) {
240 return d.frametime});
242 var latencyData = getLatencyData(this.model_, rangeOfInterest);
243 var averageLatency = tvcm.Statistics.mean(latencyData, function(d) {
247 var latencySummaryText = document.createElement('div');
248 latencySummaryText.appendChild(tvcm.ui.createSpan({
249 textContent: 'Average Latency ' + averageLatency + 'ms',
251 resultArea.appendChild(latencySummaryText);
253 var frametimeSummaryText = document.createElement('div');
254 frametimeSummaryText.appendChild(tvcm.ui.createSpan({
255 textContent: 'Average Frame Time ' + averageFrametime + 'ms',
257 resultArea.appendChild(frametimeSummaryText);
259 if (latencyData.length != 0) {
260 this.latencyChart_ = createLatencyLineChart(latencyData,
261 'Latency Over Time');
262 resultArea.appendChild(this.latencyChart_);
265 if (frametimeData.length != 0) {
266 this.frametimeChart_ = createLatencyLineChart(frametimeData,
268 this.frametimeChart_.style.display = 'block';
269 resultArea.appendChild(this.frametimeChart_);
273 get rangeOfInterest() {
274 return this.rangeOfInterest_;
277 set rangeOfInterest(rangeOfInterest) {
278 this.rangeOfInterest_ = rangeOfInterest;
279 this.updateContents_();
283 tracing.TimelineViewSidePanel.registerPanelSubtype(InputLatencySidePanel);
286 createLatencyLineChart: createLatencyLineChart,
287 getLatencyData: getLatencyData,
288 getFrametimeData: getFrametimeData,
289 InputLatencySidePanel: InputLatencySidePanel