| <!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/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 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. |
| |
| function computeAnimationThroughput(animationExpectation) { |
| if (animationExpectation.frameEvents === undefined || |
| animationExpectation.frameEvents.length === 0) |
| throw new Error('Animation missing frameEvents ' + |
| animationExpectation.stableId); |
| |
| var durationInS = tr.b.convertUnit(animationExpectation.duration, |
| tr.b.UnitScale.Metric.MILLI, tr.b.UnitScale.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); |
| |
| var frameTimestamps = animationExpectation.frameEvents; |
| frameTimestamps = frameTimestamps.toArray().map(function(event) { |
| return event.start; |
| }); |
| |
| var absolute = true; |
| return tr.b.Statistics.timestampsDiscrepancy(frameTimestamps, absolute); |
| } |
| |
| /** |
| * @param {!tr.v.ValueSet} values |
| * @param {!tr.model.Model} model |
| * @param {!Object=} opt_options |
| */ |
| function responsivenessMetric(values, model, opt_options) { |
| var responseNumeric = new tr.v.Histogram('response latency', |
| tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, |
| tr.v.HistogramBinBoundaries.createLinear(100, 1e3, 50)); |
| var throughputNumeric = new tr.v.Histogram('animation throughput', |
| tr.b.Unit.byName.unitlessNumber_biggerIsBetter, |
| tr.v.HistogramBinBoundaries.createLinear(10, 60, 10)); |
| var frameTimeDiscrepancyNumeric = new tr.v.Histogram( |
| 'animation frameTimeDiscrepancy', |
| tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, |
| tr.v.HistogramBinBoundaries.createLinear(0, 1e3, 50). |
| addExponentialBins(1e4, 10)); |
| var 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; |
| |
| var 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; |
| } |
| var throughput = computeAnimationThroughput(ue); |
| if (throughput === undefined) |
| throw new Error('Missing throughput for ' + |
| ue.stableId); |
| |
| throughputNumeric.addSample(throughput, sampleDiagnosticMap); |
| |
| var 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 |
| }); |
| }); |
| |
| values.addHistogram(responseNumeric); |
| values.addHistogram(throughputNumeric); |
| values.addHistogram(frameTimeDiscrepancyNumeric); |
| values.addHistogram(latencyNumeric); |
| } |
| |
| tr.metrics.MetricRegistry.register(responsivenessMetric, { |
| supportsRangeOfInterest: true |
| }); |
| |
| return { |
| responsivenessMetric: responsivenessMetric, |
| }; |
| }); |
| </script> |