1<!DOCTYPE html> 2<!-- 3Copyright (c) 2015 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/statistics.html"> 9<link rel="import" href="/tracing/metrics/metric_registry.html"> 10<link rel="import" 11 href="/tracing/metrics/system_health/animation_smoothness_metric.html"> 12<link rel="import" 13 href="/tracing/metrics/system_health/animation_throughput_metric.html"> 14<link rel="import" href="/tracing/metrics/system_health/utils.html"> 15<link rel="import" href="/tracing/model/user_model/animation_expectation.html"> 16<link rel="import" href="/tracing/model/user_model/load_expectation.html"> 17<link rel="import" href="/tracing/model/user_model/response_expectation.html"> 18<link rel="import" href="/tracing/value/numeric.html"> 19<link rel="import" href="/tracing/value/value.html"> 20 21<script> 22'use strict'; 23 24tr.exportTo('tr.metrics.sh', function() { 25 // In the case of Response, Load, and DiscreteAnimation IRs, Responsiveness is 26 // derived from the time between when the user thinks they begin an interation 27 // (expectedStart) and the time when the screen first changes to reflect the 28 // interaction (actualEnd). There may be a delay between expectedStart and 29 // when chrome first starts processing the interaction (actualStart) if the 30 // main thread is busy. The user doesn't know when actualStart is, they only 31 // know when expectedStart is. User responsiveness, by definition, considers 32 // only what the user experiences, so "duration" is defined as actualEnd - 33 // expectedStart. 34 35 // This histogram represents the number of people who we believe would 36 // score the responsiveness at a certain value. We have set this with 37 // just a best-effort guess, though. In #1696, we plan to derive this 38 // experimentally. 39 var RESPONSE_HISTOGRAM = tr.v.Numeric.fromDict({ 40 unit: 'unitless', 41 min: 150, 42 max: 5000, 43 centralBinWidth: 485, 44 underflowBin: {min: -Number.MAX_VALUE, max: 150, count: 1000}, 45 centralBins: [ 46 {min: 150, max: 635, count: 708}, 47 {min: 635, max: 1120, count: 223}, 48 {min: 1120, max: 1605, count: 50}, 49 {min: 1605, max: 2090, count: 33}, 50 {min: 2090, max: 2575, count: 23}, 51 {min: 2575, max: 3060, count: 17}, 52 {min: 3060, max: 3545, count: 12}, 53 {min: 3545, max: 4030, count: 8}, 54 {min: 4030, max: 4515, count: 4}, 55 {min: 4515, max: 5000, count: 1} 56 ], 57 overflowBin: {min: 5000, max: Number.MAX_VALUE, count: 0} 58 }); 59 60 var FAST_RESPONSE_HISTOGRAM = tr.v.Numeric.fromDict({ 61 unit: 'unitless', 62 min: 66, 63 max: 2200, 64 centralBinWidth: 214, 65 underflowBin: {min: -Number.MAX_VALUE, max: 66, count: 1000}, 66 centralBins: [ 67 {min: 66, max: 280, count: 708}, 68 {min: 280, max: 493, count: 223}, 69 {min: 493, max: 706, count: 50}, 70 {min: 706, max: 920, count: 33}, 71 {min: 920, max: 1133, count: 23}, 72 {min: 1133, max: 1346, count: 17}, 73 {min: 1346, max: 1560, count: 12}, 74 {min: 1560, max: 1773, count: 8}, 75 {min: 1773, max: 1987, count: 4}, 76 {min: 1987, max: 2200, count: 1} 77 ], 78 overflowBin: {min: 2200, max: Number.MAX_VALUE, count: 0} 79 }); 80 81 var LOAD_HISTOGRAM = tr.v.Numeric.fromDict({ 82 unit: 'unitless', 83 min: 1000, 84 max: 60000, 85 centralBinWidth: 5900, 86 underflowBin: {min: -Number.MAX_VALUE, max: 1000, count: 1000}, 87 centralBins: [ 88 {min: 1000, max: 6900, count: 901}, 89 {min: 6900, max: 12800, count: 574}, 90 {min: 12800, max: 18700, count: 298}, 91 {min: 18700, max: 24600, count: 65}, 92 {min: 24600, max: 30500, count: 35}, 93 {min: 30500, max: 36400, count: 23}, 94 {min: 36400, max: 42300, count: 16}, 95 {min: 42300, max: 48200, count: 10}, 96 {min: 48200, max: 54100, count: 5}, 97 {min: 54100, max: 60000, count: 2} 98 ], 99 overflowBin: {min: 60000, max: Number.MAX_VALUE, count: 0} 100 }); 101 102 var UNIT = tr.v.Unit.byName.normalizedPercentage_biggerIsBetter; 103 104 var DESCRIPTION = ( 105 'For Load and Response, Mean Opinion Score of completion time; ' + 106 'For Animation, perceptual blend of Mean Opinion Scores of ' + 107 'throughput and smoothness'); 108 109 function getDurationScore(histogram, duration) { 110 return histogram.getInterpolatedCountAt(duration) / histogram.maxCount; 111 } 112 113 function ResponsivenessMetric(valueList, model) { 114 tr.metrics.sh.AnimationThroughputMetric(valueList, model); 115 tr.metrics.sh.AnimationSmoothnessMetric(valueList, model); 116 117 var throughputForAnimation = {}; 118 var smoothnessForAnimation = {}; 119 valueList.valueDicts.forEach(function(value) { 120 if ((value.type !== 'numeric') || 121 (value.numeric.type !== 'scalar')) 122 return; 123 124 var ue = value.grouping_keys.userExpectationStableId; 125 126 if (value.grouping_keys.name === 'throughput') 127 throughputForAnimation[ue] = value.numeric.value; 128 if (value.grouping_keys.name === 'smoothness') 129 smoothnessForAnimation[ue] = value.numeric.value; 130 }); 131 132 var scores = []; 133 134 model.userModel.expectations.forEach(function(ue) { 135 var score = undefined; 136 137 if (ue instanceof tr.model.um.IdleExpectation) { 138 // Responsiveness is not defined for Idle. 139 return; 140 } else if (ue instanceof tr.model.um.LoadExpectation) { 141 score = getDurationScore(LOAD_HISTOGRAM, ue.duration); 142 } else if (ue instanceof tr.model.um.ResponseExpectation) { 143 var histogram = RESPONSE_HISTOGRAM; 144 if (ue.isAnimationBegin) 145 histogram = FAST_RESPONSE_HISTOGRAM; 146 147 score = getDurationScore(histogram, ue.duration); 148 } else if (ue instanceof tr.model.um.AnimationExpectation) { 149 var throughput = throughputForAnimation[ue.stableId]; 150 var smoothness = smoothnessForAnimation[ue.stableId]; 151 152 if (throughput === undefined) 153 throw new Error('Missing throughput for ' + ue.stableId); 154 155 if (smoothness === undefined) 156 throw new Error('Missing smoothness for ' + ue.stableId); 157 158 score = tr.b.Statistics.weightedMean( 159 [throughput, smoothness], tr.metrics.sh.perceptualBlend); 160 } else { 161 throw new Error('Unrecognized stage for ' + ue.stableId); 162 } 163 164 if (score === undefined) 165 throw new Error('Failed to compute responsiveness for ' + ue.stableId); 166 167 scores.push(score); 168 169 var options = {}; 170 options.description = DESCRIPTION; 171 172 var groupingKeys = {}; 173 groupingKeys.userExpectationStableId = ue.stableId; 174 groupingKeys.userExpectationStageTitle = ue.stageTitle; 175 groupingKeys.userExpectationInitiatorTitle = ue.initiatorTitle; 176 177 valueList.addValue(new tr.v.NumericValue( 178 model.canonicalUrlThatCreatedThisTrace, 'responsiveness', 179 new tr.v.ScalarNumeric(UNIT, score), 180 options, groupingKeys)); 181 }); 182 183 // Manually reduce scores. 184 // https://github.com/catapult-project/catapult/issues/2036 185 186 var options = {}; 187 options.description = DESCRIPTION; 188 var groupingKeys = {}; 189 var overallScore = tr.b.Statistics.weightedMean( 190 scores, tr.metrics.sh.perceptualBlend); 191 if (overallScore === undefined) 192 return; 193 194 valueList.addValue(new tr.v.NumericValue( 195 model.canonicalUrlThatCreatedThisTrace, 'responsiveness', 196 new tr.v.ScalarNumeric(UNIT, overallScore), 197 options, groupingKeys)); 198 } 199 200 ResponsivenessMetric.prototype = { 201 __proto__: Function.prototype 202 }; 203 204 tr.metrics.MetricRegistry.register(ResponsivenessMetric); 205 206 return { 207 ResponsivenessMetric: ResponsivenessMetric 208 }; 209}); 210</script> 211