blob: 8e63e4ee2a134c9e503e6e29eb1a21dd648000ff [file] [log] [blame]
<!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/chrome/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='tr-e-s-input-latency-side-panel'
extends='tr-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 tr.b.Range();
this.frametimeType_ = tr.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 tr.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 tr.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 = tr.e.audits.getSlicesIntersectingRange(r, latencySlices);
var event = new tr.c.RequestSelectionChangeEvent();
event.selection = new tr.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 browserProcess = this.modelHelper_.browserProcess;
var labels = [];
if (browserProcess !== undefined) {
var label_str = 'Browser: ' + browserProcess.pid;
labels.push({label: label_str, value: browserProcess.pid});
}
tr.b.iterItems(this.modelHelper_.rendererHelpers,
function(pid, rendererHelper) {
var rendererProcess = rendererHelper.process;
var label_str = 'Renderer: ' + rendererProcess.userFriendlyName;
labels.push({label: label_str, value: rendererProcess.userFriendlyName
});
}, this);
if (labels.length === 0)
return;
this.selectedProcessId_ = labels[0].value;
var toolbarEl = this.$.toolbar;
toolbarEl.appendChild(tr.b.ui.createSelector(
this, 'frametimeType',
'inputLatencySidePanel.frametimeType', this.frametimeType_,
[{label: 'Main Thread Frame Times',
value: tr.e.audits.MAIN_FRAMETIME_TYPE},
{label: 'Impl Thread Frame Times',
value: tr.e.audits.IMPL_FRAMETIME_TYPE}
]));
toolbarEl.appendChild(tr.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 tr.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_.rendererHelpers[this.selectedProcessId_])
chromeProcess = this.modelHelper_.rendererHelpers[
this.selectedProcessId_
];
else
chromeProcess = this.modelHelper_.browserHelper;
var frameEvents = chromeProcess.getFrameEventsInRange(
this.frametimeType, rangeOfInterest);
var frametimeData = tr.e.audits.getFrametimeDataFromEvents(frameEvents);
var averageFrametime = tr.b.Statistics.mean(frametimeData, function(d) {
return 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.Statistics.mean(latencyData, function(d) {
return d.latency;
});
// Create summary.
var latencySummaryText = document.createElement('div');
latencySummaryText.appendChild(tr.b.ui.createSpan({
textContent: 'Average Latency ' + averageLatency + ' ms',
bold: true}));
resultArea.appendChild(latencySummaryText);
var frametimeSummaryText = document.createElement('div');
frametimeSummaryText.appendChild(tr.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 (!tr.e.audits.ChromeModelHelper.supportsModel(m)) {
return {
supported: false,
reason: 'No Chrome browser or renderer process found'
};
}
var modelHelper = new tr.e.audits.ChromeModelHelper(m);
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';
}
});
</script>
</polymer-element>