| <!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> |