blob: 911c2d66d4887b17d875e67098992c56144e230d [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/math/statistics.html">
<link rel="import" href="/tracing/metrics/metric_registry.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/histogram.html">
<script>
'use strict';
tr.exportTo('tr.metrics.sh', function() {
// In the case of Response, Load, and DiscreteAnimation UEs, 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.
function computeAnimationThroughput(animationExpectation) {
if (animationExpectation.frameEvents === undefined ||
animationExpectation.frameEvents.length === 0) {
throw new Error('Animation missing frameEvents ' +
animationExpectation.stableId);
}
const durationInS = tr.b.convertUnit(animationExpectation.duration,
tr.b.UnitPrefixScale.METRIC.MILLI,
tr.b.UnitPrefixScale.METRIC.NONE);
return animationExpectation.frameEvents.length / durationInS;
}
function computeAnimationframeTimeDiscrepancy(animationExpectation) {
if (animationExpectation.frameEvents === undefined ||
animationExpectation.frameEvents.length === 0) {
throw new Error('Animation missing frameEvents ' +
animationExpectation.stableId);
}
let frameTimestamps = animationExpectation.frameEvents;
frameTimestamps = frameTimestamps.toArray().map(function(event) {
return event.start;
});
const absolute = true;
return tr.b.math.Statistics.timestampsDiscrepancy(
frameTimestamps, absolute);
}
/**
* @param {!tr.v.HistogramSet} histograms
* @param {!tr.model.Model} model
* @param {!Object=} opt_options
*/
function responsivenessMetric(histograms, model, opt_options) {
const responseNumeric = new tr.v.Histogram('response latency',
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
tr.v.HistogramBinBoundaries.createLinear(100, 1e3, 50));
const throughputNumeric = new tr.v.Histogram('animation throughput',
tr.b.Unit.byName.unitlessNumber_biggerIsBetter,
tr.v.HistogramBinBoundaries.createLinear(10, 60, 10));
const frameTimeDiscrepancyNumeric = new tr.v.Histogram(
'animation frameTimeDiscrepancy',
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
tr.v.HistogramBinBoundaries.createLinear(0, 1e3, 50).
addExponentialBins(1e4, 10));
const latencyNumeric = new tr.v.Histogram('animation latency',
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
tr.v.HistogramBinBoundaries.createLinear(0, 300, 60));
model.userModel.expectations.forEach(function(ue) {
if (opt_options && opt_options.rangeOfInterest &&
!opt_options.rangeOfInterest.intersectsExplicitRangeInclusive(
ue.start, ue.end)) {
return;
}
const sampleDiagnosticMap = tr.v.d.DiagnosticMap.fromObject(
{relatedEvents: new tr.v.d.RelatedEventSet([ue])});
// Responsiveness is not defined for Idle or Startup expectations.
if (ue instanceof tr.model.um.IdleExpectation) {
return;
} else if (ue instanceof tr.model.um.StartupExpectation) {
return;
} else if (ue instanceof tr.model.um.LoadExpectation) {
// This is already covered by loadingMetric.
} else if (ue instanceof tr.model.um.ResponseExpectation) {
responseNumeric.addSample(ue.duration, sampleDiagnosticMap);
} else if (ue instanceof tr.model.um.AnimationExpectation) {
if (ue.frameEvents === undefined || ue.frameEvents.length === 0) {
// Ignore animation stages that do not have associated frames:
// https://github.com/catapult-project/catapult/issues/2446
return;
}
const throughput = computeAnimationThroughput(ue);
if (throughput === undefined) {
throw new Error('Missing throughput for ' +
ue.stableId);
}
throughputNumeric.addSample(throughput, sampleDiagnosticMap);
const frameTimeDiscrepancy = computeAnimationframeTimeDiscrepancy(ue);
if (frameTimeDiscrepancy === undefined) {
throw new Error('Missing frameTimeDiscrepancy for ' +
ue.stableId);
}
frameTimeDiscrepancyNumeric.addSample(
frameTimeDiscrepancy, sampleDiagnosticMap);
ue.associatedEvents.forEach(function(event) {
if (!(event instanceof tr.e.cc.InputLatencyAsyncSlice)) {
return;
}
latencyNumeric.addSample(event.duration, sampleDiagnosticMap);
});
} else {
throw new Error('Unrecognized stage for ' + ue.stableId);
}
});
[
responseNumeric, throughputNumeric, frameTimeDiscrepancyNumeric,
latencyNumeric
].forEach(function(numeric) {
numeric.customizeSummaryOptions({
avg: true,
max: true,
min: true,
std: true
});
});
histograms.addHistogram(responseNumeric);
histograms.addHistogram(throughputNumeric);
histograms.addHistogram(frameTimeDiscrepancyNumeric);
histograms.addHistogram(latencyNumeric);
}
tr.metrics.MetricRegistry.register(responsivenessMetric, {
supportsRangeOfInterest: true,
requiredCategories: ['rail'],
});
return {
responsivenessMetric,
};
});
</script>