blob: 793427481e3d70d0c61d067341a1df94bcadc028 [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="/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>