| <!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="/extras/audits/chrome_model_helper.html"> |
| <link rel="import" href="/core/side_panel/side_panel.html"> |
| <link rel="import" href="/core/selection.html"> |
| <link rel="import" href="/base/statistics.html"> |
| <link rel="import" href="/base/ui/dom_helpers.html"> |
| <link rel="import" href="/base/ui/line_chart.html"> |
| |
| <polymer-element name='tv-e-side-panel-input-latency' extends='tv-c-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> |
| |
| <script> |
| 'use strict'; |
| |
| Polymer({ |
| ready: function() { |
| this.rangeOfInterest_ = new tv.b.Range(); |
| this.frametimeType_ = tv.e.audits.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_ = new tv.e.audits.ChromeModelHelper(model); |
| 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 tv.b.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 = []; |
| this.model_.getAllThreads().forEach(function(thread) { |
| thread.iterateAllEvents(function(event) { |
| if (event.title.indexOf('InputLatency:') === 0) |
| latencySlices.push(event); |
| }); |
| }); |
| latencySlices = tv.e.audits.getSlicesIntersectingRange(r, latencySlices); |
| |
| var event = new tv.c.RequestSelectionChangeEvent(); |
| event.selection = new tv.c.Selection(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 rendererProcesses = this.modelHelper_.rendererProcesses; |
| var browserProcess = this.modelHelper_.browserProcess; |
| var labels = []; |
| |
| if (browserProcess !== undefined) { |
| var label_str = 'Browser: ' + browserProcess.pid; |
| labels.push({label: label_str, value: browserProcess.pid}); |
| } |
| |
| rendererProcesses.forEach(function(rendererProcess) { |
| var label_str = 'Renderer: ' + rendererProcess.pid; |
| labels.push({label: label_str, value: rendererProcess.pid}); |
| }); |
| |
| if (labels.length === 0) |
| return; |
| |
| this.selectedProcessId_ = labels[0].value; |
| var toolbarEl = this.$.toolbar; |
| toolbarEl.appendChild(tv.b.ui.createSelector( |
| this, 'frametimeType', |
| 'inputLatencySidePanel.frametimeType', this.frametimeType_, |
| [{label: 'Main Thread Frame Times', |
| value: tv.e.audits.MAIN_FRAMETIME_TYPE}, |
| {label: 'Impl Thread Frame Times', |
| value: tv.e.audits.IMPL_FRAMETIME_TYPE} |
| ])); |
| toolbarEl.appendChild(tv.b.ui.createSelector( |
| this, 'selectedProcessId', |
| 'inputLatencySidePanel.selectedProcessId', |
| this.selectedProcessId_, |
| labels)); |
| }, |
| |
| get currentRangeOfInterest() { |
| if (this.rangeOfInterest_.isEmpty) |
| return this.model_.bounds; |
| else |
| return this.rangeOfInterest_; |
| }, |
| |
| createLatencyLineChart: function(data, title) { |
| var chart = new tv.b.ui.LineChart(); |
| var width = 600; |
| if (document.body.clientWidth != undefined) |
| width = document.body.clientWidth * 0.5; |
| chart.setSize({width: width, height: chart.height}); |
| chart.chartTitle = title; |
| chart.data = data; |
| return chart; |
| }, |
| |
| updateContents_: function() { |
| var resultArea = this.$.result_area; |
| this.latencyChart_ = undefined; |
| this.frametimeChart_ = undefined; |
| resultArea.textContent = ''; |
| |
| if (this.modelHelper_ === undefined) |
| return; |
| |
| var rangeOfInterest = this.currentRangeOfInterest; |
| |
| var chromeProcess; |
| if (this.modelHelper_.renderers[this.selectedProcessId_]) |
| chromeProcess = this.modelHelper_.renderers[this.selectedProcessId_]; |
| else |
| chromeProcess = this.modelHelper_.browser; |
| |
| var frameEvents = chromeProcess.getFrameEventsInRange( |
| this.frametimeType, rangeOfInterest); |
| |
| var frametimeData = tv.e.audits.getFrametimeDataFromEvents(frameEvents); |
| var averageFrametime = tv.b.Statistics.mean(frametimeData, function(d) { |
| return d.frametime; |
| }); |
| |
| var latencyEvents = this.modelHelper_.browser.getLatencyEventsInRange( |
| rangeOfInterest); |
| |
| var latencyData = []; |
| latencyEvents.forEach(function(event) { |
| if (event.inputLatency === undefined) |
| return; |
| latencyData.push({ |
| x: event.start, |
| latency: event.inputLatency / 1000 |
| }); |
| }); |
| |
| var averageLatency = tv.b.Statistics.mean(latencyData, function(d) { |
| return d.latency; |
| }); |
| |
| // Create summary. |
| var latencySummaryText = document.createElement('div'); |
| latencySummaryText.appendChild(tv.b.ui.createSpan({ |
| textContent: 'Average Latency ' + averageLatency + ' ms', |
| bold: true})); |
| resultArea.appendChild(latencySummaryText); |
| |
| var frametimeSummaryText = document.createElement('div'); |
| frametimeSummaryText.appendChild(tv.b.ui.createSpan({ |
| textContent: 'Average Frame Time ' + averageFrametime + ' ms', |
| bold: true})); |
| resultArea.appendChild(frametimeSummaryText); |
| |
| if (latencyData.length !== 0) { |
| this.latencyChart_ = this.createLatencyLineChart( |
| latencyData, 'Latency Over Time'); |
| this.registerMouseEventForLatencyChart_(); |
| resultArea.appendChild(this.latencyChart_); |
| } |
| |
| if (frametimeData.length != 0) { |
| this.frametimeChart_ = this.createLatencyLineChart( |
| frametimeData, 'Frame Times'); |
| this.frametimeChart_.style.display = 'block'; |
| resultArea.appendChild(this.frametimeChart_); |
| } |
| }, |
| |
| 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 (!tv.e.audits.ChromeModelHelper.supportsModel(m)) { |
| return { |
| supported: false, |
| reason: 'No Chrome browser or renderer process found' |
| }; |
| } |
| |
| var modelHelper = new tv.e.audits.ChromeModelHelper(m); |
| if (modelHelper.browser && modelHelper.browser.hasLatencyEvents) { |
| return { |
| supported: true |
| }; |
| } |
| |
| return { |
| supported: false, |
| reason: 'No InputLatency events trace. Consider enabling ' + |
| 'benchmark" and "input" category when recording the trace' |
| }; |
| }, |
| |
| textLabel: 'Input Latency' |
| }); |
| </script> |
| </polymer-element> |