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