blob: eb0ac8f2f5abd0f8ce85340bb1e1ed6fcb8c4e3e [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2015 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/statistics.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import"
href="/tracing/metrics/system_health/animation_smoothness_metric.html">
<link rel="import"
href="/tracing/metrics/system_health/animation_throughput_metric.html">
<link rel="import" href="/tracing/metrics/system_health/utils.html">
<link rel="import" href="/tracing/model/user_model/animation_expectation.html">
<link rel="import" href="/tracing/model/user_model/load_expectation.html">
<link rel="import" href="/tracing/model/user_model/response_expectation.html">
<link rel="import" href="/tracing/value/numeric.html">
<link rel="import" href="/tracing/value/value.html">
<script>
'use strict';
tr.exportTo('tr.metrics.sh', function() {
// In the case of Response, Load, and DiscreteAnimation IRs, Responsiveness is
// derived from the time between when the user thinks they begin an interation
// (expectedStart) and the time when the screen first changes to reflect the
// interaction (actualEnd). There may be a delay between expectedStart and
// when chrome first starts processing the interaction (actualStart) if the
// main thread is busy. The user doesn't know when actualStart is, they only
// know when expectedStart is. User responsiveness, by definition, considers
// only what the user experiences, so "duration" is defined as actualEnd -
// expectedStart.
// This histogram represents the number of people who we believe would
// score the responsiveness at a certain value. We have set this with
// just a best-effort guess, though. In #1696, we plan to derive this
// experimentally.
var RESPONSE_HISTOGRAM = tr.v.Numeric.fromDict({
unit: 'unitless',
min: 150,
max: 5000,
centralBinWidth: 485,
underflowBin: {min: -Number.MAX_VALUE, max: 150, count: 1000},
centralBins: [
{min: 150, max: 635, count: 708},
{min: 635, max: 1120, count: 223},
{min: 1120, max: 1605, count: 50},
{min: 1605, max: 2090, count: 33},
{min: 2090, max: 2575, count: 23},
{min: 2575, max: 3060, count: 17},
{min: 3060, max: 3545, count: 12},
{min: 3545, max: 4030, count: 8},
{min: 4030, max: 4515, count: 4},
{min: 4515, max: 5000, count: 1}
],
overflowBin: {min: 5000, max: Number.MAX_VALUE, count: 0}
});
var FAST_RESPONSE_HISTOGRAM = tr.v.Numeric.fromDict({
unit: 'unitless',
min: 66,
max: 2200,
centralBinWidth: 214,
underflowBin: {min: -Number.MAX_VALUE, max: 66, count: 1000},
centralBins: [
{min: 66, max: 280, count: 708},
{min: 280, max: 493, count: 223},
{min: 493, max: 706, count: 50},
{min: 706, max: 920, count: 33},
{min: 920, max: 1133, count: 23},
{min: 1133, max: 1346, count: 17},
{min: 1346, max: 1560, count: 12},
{min: 1560, max: 1773, count: 8},
{min: 1773, max: 1987, count: 4},
{min: 1987, max: 2200, count: 1}
],
overflowBin: {min: 2200, max: Number.MAX_VALUE, count: 0}
});
var LOAD_HISTOGRAM = tr.v.Numeric.fromDict({
unit: 'unitless',
min: 1000,
max: 60000,
centralBinWidth: 5900,
underflowBin: {min: -Number.MAX_VALUE, max: 1000, count: 1000},
centralBins: [
{min: 1000, max: 6900, count: 901},
{min: 6900, max: 12800, count: 574},
{min: 12800, max: 18700, count: 298},
{min: 18700, max: 24600, count: 65},
{min: 24600, max: 30500, count: 35},
{min: 30500, max: 36400, count: 23},
{min: 36400, max: 42300, count: 16},
{min: 42300, max: 48200, count: 10},
{min: 48200, max: 54100, count: 5},
{min: 54100, max: 60000, count: 2}
],
overflowBin: {min: 60000, max: Number.MAX_VALUE, count: 0}
});
var UNIT = tr.v.Unit.byName.normalizedPercentage_biggerIsBetter;
var DESCRIPTION = (
'For Load and Response, Mean Opinion Score of completion time; ' +
'For Animation, perceptual blend of Mean Opinion Scores of ' +
'throughput and smoothness');
function getDurationScore(histogram, duration) {
return histogram.getInterpolatedCountAt(duration) / histogram.maxCount;
}
function ResponsivenessMetric(valueList, model) {
tr.metrics.sh.AnimationThroughputMetric(valueList, model);
tr.metrics.sh.AnimationSmoothnessMetric(valueList, model);
var throughputForAnimation = {};
var smoothnessForAnimation = {};
valueList.valueDicts.forEach(function(value) {
if ((value.type !== 'numeric') ||
(value.numeric.type !== 'scalar'))
return;
var ue = value.grouping_keys.userExpectationStableId;
if (value.grouping_keys.name === 'throughput')
throughputForAnimation[ue] = value.numeric.value;
if (value.grouping_keys.name === 'smoothness')
smoothnessForAnimation[ue] = value.numeric.value;
});
var scores = [];
model.userModel.expectations.forEach(function(ue) {
var score = undefined;
if (ue instanceof tr.model.um.IdleExpectation) {
// Responsiveness is not defined for Idle.
return;
} else if (ue instanceof tr.model.um.LoadExpectation) {
score = getDurationScore(LOAD_HISTOGRAM, ue.duration);
} else if (ue instanceof tr.model.um.ResponseExpectation) {
var histogram = RESPONSE_HISTOGRAM;
if (ue.isAnimationBegin)
histogram = FAST_RESPONSE_HISTOGRAM;
score = getDurationScore(histogram, ue.duration);
} else if (ue instanceof tr.model.um.AnimationExpectation) {
var throughput = throughputForAnimation[ue.stableId];
var smoothness = smoothnessForAnimation[ue.stableId];
if (throughput === undefined)
throw new Error('Missing throughput for ' + ue.stableId);
if (smoothness === undefined)
throw new Error('Missing smoothness for ' + ue.stableId);
score = tr.b.Statistics.weightedMean(
[throughput, smoothness], tr.metrics.sh.perceptualBlend);
} else {
throw new Error('Unrecognized stage for ' + ue.stableId);
}
if (score === undefined)
throw new Error('Failed to compute responsiveness for ' + ue.stableId);
scores.push(score);
var options = {};
options.description = DESCRIPTION;
var groupingKeys = {};
groupingKeys.userExpectationStableId = ue.stableId;
groupingKeys.userExpectationStageTitle = ue.stageTitle;
groupingKeys.userExpectationInitiatorTitle = ue.initiatorTitle;
valueList.addValue(new tr.v.NumericValue(
model.canonicalUrlThatCreatedThisTrace, 'responsiveness',
new tr.v.ScalarNumeric(UNIT, score),
options, groupingKeys));
});
// Manually reduce scores.
// https://github.com/catapult-project/catapult/issues/2036
var options = {};
options.description = DESCRIPTION;
var groupingKeys = {};
var overallScore = tr.b.Statistics.weightedMean(
scores, tr.metrics.sh.perceptualBlend);
if (overallScore === undefined)
return;
valueList.addValue(new tr.v.NumericValue(
model.canonicalUrlThatCreatedThisTrace, 'responsiveness',
new tr.v.ScalarNumeric(UNIT, overallScore),
options, groupingKeys));
}
ResponsivenessMetric.prototype = {
__proto__: Function.prototype
};
tr.metrics.MetricRegistry.register(ResponsivenessMetric);
return {
ResponsivenessMetric: ResponsivenessMetric
};
});
</script>