| <!DOCTYPE html> |
| <!-- |
| Copyright 2014 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/tracing/base/math/statistics.html"> |
| <link rel="import" href="/tracing/model/event_set.html"> |
| <link rel="import" href="/tracing/model/helpers/chrome_model_helper.html"> |
| <link rel="import" href="/tracing/ui/base/dom_helpers.html"> |
| <link rel="import" href="/tracing/ui/base/line_chart.html"> |
| <link rel="import" href="/tracing/ui/side_panel/side_panel.html"> |
| <link rel="import" href="/tracing/ui/side_panel/side_panel_registry.html"> |
| |
| <dom-module id='tr-ui-e-s-input-latency-side-panel'> |
| <template> |
| <style> |
| :host { |
| flex-direction: column; |
| display: flex; |
| } |
| toolbar { |
| flex: 0 0 auto; |
| border-bottom: 1px solid black; |
| display: flex; |
| } |
| result-area { |
| flex: 1 1 auto; |
| display: block; |
| min-height: 0; |
| overflow-y: auto; |
| } |
| </style> |
| |
| <toolbar id='toolbar'></toolbar> |
| <result-area id='result_area'></result-area> |
| </template> |
| </dom-module> |
| <script> |
| 'use strict'; |
| |
| Polymer({ |
| is: 'tr-ui-e-s-input-latency-side-panel', |
| behaviors: [tr.ui.behaviors.SidePanel], |
| |
| |
| ready: function() { |
| this.rangeOfInterest_ = new tr.b.math.Range(); |
| this.frametimeType_ = tr.model.helpers.IMPL_FRAMETIME_TYPE; |
| this.latencyChart_ = undefined; |
| this.frametimeChart_ = undefined; |
| this.selectedProcessId_ = undefined; |
| this.mouseDownIndex_ = undefined; |
| this.curMouseIndex_ = undefined; |
| }, |
| |
| get model() { |
| return this.model_; |
| }, |
| |
| set model(model) { |
| this.model_ = model; |
| if (this.model_) { |
| this.modelHelper_ = this.model_.getOrCreateHelper( |
| tr.model.helpers.ChromeModelHelper); |
| } else { |
| this.modelHelper_ = undefined; |
| } |
| |
| this.updateToolbar_(); |
| this.updateContents_(); |
| }, |
| |
| get frametimeType() { |
| return this.frametimeType_; |
| }, |
| |
| set frametimeType(type) { |
| if (this.frametimeType_ === type) |
| return; |
| this.frametimeType_ = type; |
| this.updateContents_(); |
| }, |
| |
| get selectedProcessId() { |
| return this.selectedProcessId_; |
| }, |
| |
| set selectedProcessId(process) { |
| if (this.selectedProcessId_ === process) |
| return; |
| this.selectedProcessId_ = process; |
| this.updateContents_(); |
| }, |
| |
| set selection(selection) { |
| if (this.latencyChart_ === undefined) |
| return; |
| this.latencyChart_.brushedRange = selection.bounds; |
| }, |
| |
| // This function is for testing purpose. |
| setBrushedIndices: function(mouseDownIndex, curIndex) { |
| this.mouseDownIndex_ = mouseDownIndex; |
| this.curMouseIndex_ = curIndex; |
| this.updateBrushedRange_(); |
| }, |
| |
| updateBrushedRange_: function() { |
| if (this.latencyChart_ === undefined) |
| return; |
| |
| var r = new tr.b.math.Range(); |
| if (this.mouseDownIndex_ === undefined) { |
| this.latencyChart_.brushedRange = r; |
| return; |
| } |
| r = this.latencyChart_.computeBrushRangeFromIndices( |
| this.mouseDownIndex_, this.curMouseIndex_); |
| this.latencyChart_.brushedRange = r; |
| |
| // Based on the brushed range, update the selection of LatencyInfo in |
| // the timeline view by sending a selectionChange event. |
| var latencySlices = []; |
| for (var thread of this.model_.getAllThreads()) |
| for (var event of thread.getDescendantEvents()) |
| if (event.title.indexOf('InputLatency:') === 0) |
| latencySlices.push(event); |
| latencySlices = tr.model.helpers.getSlicesIntersectingRange( |
| r, latencySlices); |
| |
| var event = new tr.model.RequestSelectionChangeEvent(); |
| event.selection = new tr.model.EventSet(latencySlices); |
| this.latencyChart_.dispatchEvent(event); |
| }, |
| |
| registerMouseEventForLatencyChart_: function() { |
| this.latencyChart_.addEventListener('item-mousedown', function(e) { |
| this.mouseDownIndex_ = e.index; |
| this.curMouseIndex_ = e.index; |
| this.updateBrushedRange_(); |
| }.bind(this)); |
| |
| this.latencyChart_.addEventListener('item-mousemove', function(e) { |
| if (e.button === undefined) |
| return; |
| this.curMouseIndex_ = e.index; |
| this.updateBrushedRange_(); |
| }.bind(this)); |
| |
| this.latencyChart_.addEventListener('item-mouseup', function(e) { |
| this.curMouseIndex = e.index; |
| this.updateBrushedRange_(); |
| }.bind(this)); |
| }, |
| |
| updateToolbar_: function() { |
| var browserProcess = this.modelHelper_.browserProcess; |
| var labels = []; |
| |
| if (browserProcess !== undefined) { |
| var labelStr = 'Browser: ' + browserProcess.pid; |
| labels.push({label: labelStr, value: browserProcess.pid}); |
| } |
| |
| for (var rendererHelper of |
| Object.values(this.modelHelper_.rendererHelpers)) { |
| var rendererProcess = rendererHelper.process; |
| var labelStr = 'Renderer: ' + rendererProcess.userFriendlyName; |
| labels.push({label: labelStr, value: rendererProcess.userFriendlyName}); |
| } |
| |
| if (labels.length === 0) |
| return; |
| |
| this.selectedProcessId_ = labels[0].value; |
| var toolbarEl = this.$.toolbar; |
| Polymer.dom(toolbarEl).appendChild(tr.ui.b.createSelector( |
| this, 'frametimeType', |
| 'inputLatencySidePanel.frametimeType', this.frametimeType_, |
| [{label: 'Main Thread Frame Times', |
| value: tr.model.helpers.MAIN_FRAMETIME_TYPE}, |
| {label: 'Impl Thread Frame Times', |
| value: tr.model.helpers.IMPL_FRAMETIME_TYPE} |
| ])); |
| Polymer.dom(toolbarEl).appendChild(tr.ui.b.createSelector( |
| this, 'selectedProcessId', |
| 'inputLatencySidePanel.selectedProcessId', |
| this.selectedProcessId_, |
| labels)); |
| }, |
| |
| // TODO(charliea): Delete this function in favor of rangeOfInterest. |
| get currentRangeOfInterest() { |
| if (this.rangeOfInterest_.isEmpty) |
| return this.model_.bounds; |
| return this.rangeOfInterest_; |
| }, |
| |
| createLatencyLineChart: function(data, title, parentNode) { |
| var chart = new tr.ui.b.LineChart(); |
| Polymer.dom(parentNode).appendChild(chart); |
| var width = 600; |
| if (document.body.clientWidth !== undefined) { |
| width = document.body.clientWidth * 0.5; |
| } |
| chart.graphWidth = width; |
| chart.chartTitle = title; |
| chart.data = data; |
| return chart; |
| }, |
| |
| updateContents_: function() { |
| var resultArea = this.$.result_area; |
| this.latencyChart_ = undefined; |
| this.frametimeChart_ = undefined; |
| Polymer.dom(resultArea).textContent = ''; |
| |
| if (this.modelHelper_ === undefined) |
| return; |
| |
| var rangeOfInterest = this.currentRangeOfInterest; |
| |
| var chromeProcess; |
| if (this.modelHelper_.rendererHelpers[this.selectedProcessId_]) |
| chromeProcess = this.modelHelper_.rendererHelpers[ |
| this.selectedProcessId_ |
| ]; |
| else |
| chromeProcess = this.modelHelper_.browserHelper; |
| |
| var frameEvents = chromeProcess.getFrameEventsInRange( |
| this.frametimeType, rangeOfInterest); |
| |
| var frametimeData = tr.model.helpers.getFrametimeDataFromEvents( |
| frameEvents); |
| var averageFrametime = tr.b.math.Statistics.mean(frametimeData, d => |
| d.frametime |
| ); |
| |
| var latencyEvents = this.modelHelper_.browserHelper. |
| getLatencyEventsInRange( |
| rangeOfInterest); |
| |
| var latencyData = []; |
| latencyEvents.forEach(function(event) { |
| if (event.inputLatency === undefined) |
| return; |
| latencyData.push({ |
| x: event.start, |
| latency: event.inputLatency / 1000 |
| }); |
| }); |
| |
| var averageLatency = tr.b.math.Statistics.mean(latencyData, function(d) { |
| return d.latency; |
| }); |
| |
| // Create summary. |
| var latencySummaryText = document.createElement('div'); |
| Polymer.dom(latencySummaryText).appendChild(tr.ui.b.createSpan({ |
| textContent: 'Average Latency ' + averageLatency + ' ms', |
| bold: true})); |
| Polymer.dom(resultArea).appendChild(latencySummaryText); |
| |
| var frametimeSummaryText = document.createElement('div'); |
| Polymer.dom(frametimeSummaryText).appendChild(tr.ui.b.createSpan({ |
| textContent: 'Average Frame Time ' + averageFrametime + ' ms', |
| bold: true})); |
| Polymer.dom(resultArea).appendChild(frametimeSummaryText); |
| |
| if (latencyData.length !== 0) { |
| this.latencyChart_ = this.createLatencyLineChart( |
| latencyData, 'Latency Over Time', resultArea); |
| this.registerMouseEventForLatencyChart_(); |
| } |
| |
| if (frametimeData.length !== 0) { |
| this.frametimeChart_ = this.createLatencyLineChart( |
| frametimeData, 'Frame Times', resultArea); |
| } |
| }, |
| |
| get rangeOfInterest() { |
| return this.rangeOfInterest_; |
| }, |
| |
| set rangeOfInterest(rangeOfInterest) { |
| this.rangeOfInterest_ = rangeOfInterest; |
| this.updateContents_(); |
| }, |
| |
| supportsModel: function(m) { |
| if (m === undefined) { |
| return { |
| supported: false, |
| reason: 'Unknown tracing model' |
| }; |
| } |
| |
| if (!tr.model.helpers.ChromeModelHelper.supportsModel(m)) { |
| return { |
| supported: false, |
| reason: 'No Chrome browser or renderer process found' |
| }; |
| } |
| |
| var modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper); |
| if (modelHelper.browserHelper && |
| modelHelper.browserHelper.hasLatencyEvents) { |
| return { |
| supported: true |
| }; |
| } |
| |
| return { |
| supported: false, |
| reason: 'No InputLatency events trace. Consider enabling ' + |
| 'benchmark" and "input" category when recording the trace' |
| }; |
| }, |
| |
| get textLabel() { |
| return 'Input Latency'; |
| } |
| }); |
| |
| tr.ui.side_panel.SidePanelRegistry.register(function() { |
| return document.createElement('tr-ui-e-s-input-latency-side-panel'); |
| }); |
| </script> |