OLD | NEW |
1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
2 <!-- | 2 <!-- |
3 Copyright (c) 2015 The Chromium Authors. All rights reserved. | 3 Copyright (c) 2015 The Chromium Authors. All rights reserved. |
4 Use of this source code is governed by a BSD-style license that can be | 4 Use of this source code is governed by a BSD-style license that can be |
5 found in the LICENSE file. | 5 found in the LICENSE file. |
6 --> | 6 --> |
7 | 7 |
8 <link rel="import" href="/tracing/metrics/metric_registry.html"> | 8 <link rel="import" href="/tracing/metrics/metric_registry.html"> |
9 <link rel="import" | 9 <link rel="import" href="/tracing/metrics/system_health/long_tasks_metric.html"> |
10 href="/tracing/metrics/system_health/responsiveness_metric.html"> | |
11 <link rel="import" href="/tracing/model/user_model/idle_expectation.html"> | |
12 <link rel="import" href="/tracing/value/numeric.html"> | 10 <link rel="import" href="/tracing/value/numeric.html"> |
13 <link rel="import" href="/tracing/value/value.html"> | 11 <link rel="import" href="/tracing/value/value.html"> |
14 | 12 |
15 <script> | 13 <script> |
16 'use strict'; | 14 'use strict'; |
17 | 15 |
18 tr.exportTo('tr.metrics.sh', function() { | 16 tr.exportTo('tr.metrics.sh', function() { |
19 var LONG_TASK_MS = 50; | |
20 | |
21 var normalizedPercentage_smallerIsBetter = | 17 var normalizedPercentage_smallerIsBetter = |
22 tr.v.Unit.byName.normalizedPercentage_smallerIsBetter; | 18 tr.v.Unit.byName.normalizedPercentage_smallerIsBetter; |
23 var timeDurationInMs_smallerIsBetter = | |
24 tr.v.Unit.byName.timeDurationInMs_smallerIsBetter; | |
25 | 19 |
26 var FAST_RESPONSE_HISTOGRAM = tr.v.Numeric.fromDict({ | 20 // The following math is easier if the units are seconds rather than ms, |
27 unit: 'unitless', | 21 // so durations will be converted from ms to s. |
28 min: 66, | 22 var MS_PER_S = 1000; |
29 max: 2200, | |
30 centralBinWidth: 214, | |
31 underflowBin: {min: -Number.MAX_VALUE, max: 66, count: 1000}, | |
32 centralBins: [ | |
33 {min: 66, max: 280, count: 708}, | |
34 {min: 280, max: 493, count: 223}, | |
35 {min: 493, max: 706, count: 50}, | |
36 {min: 706, max: 920, count: 33}, | |
37 {min: 920, max: 1133, count: 23}, | |
38 {min: 1133, max: 1346, count: 17}, | |
39 {min: 1346, max: 1560, count: 12}, | |
40 {min: 1560, max: 1773, count: 8}, | |
41 {min: 1773, max: 1987, count: 4}, | |
42 {min: 1987, max: 2200, count: 1} | |
43 ], | |
44 overflowBin: {min: 2200, max: Number.MAX_VALUE, count: 0} | |
45 }); | |
46 | 23 |
47 function computeDurationResponsiveness(histogram, duration) { | 24 // https://www.desmos.com/calculator/ysabhcc42g |
48 return histogram.getInterpolatedCountAt(duration) / histogram.maxCount; | 25 var RESPONSE_RISK = |
49 } | 26 tr.b.Statistics.LogNormalDistribution.fromMedianAndDiminishingReturns( |
| 27 100 / MS_PER_S, 50 / MS_PER_S); |
50 | 28 |
51 function findLongTasks(ue) { | 29 /** |
52 var longTasks = []; | 30 * This helper function computes the risk that a task of the given duration |
53 // NB: This misses tasks that were associated with another UE, | 31 * would impact the responsiveness of a speculative input. |
54 // since only unassociated events are vacuumed up into IdleExpectations. | 32 * |
55 ue.associatedEvents.forEach(function(event) { | 33 * @param {number} durationMs |
56 if ((event instanceof tr.model.ThreadSlice) && | 34 * @return {number} task hazard |
57 (event.duration > LONG_TASK_MS) && | 35 */ |
58 event.isTopLevel) | |
59 longTasks.push(event); | |
60 }); | |
61 return longTasks; | |
62 } | |
63 | |
64 function computeResponsivenessRisk(durationMs) { | 36 function computeResponsivenessRisk(durationMs) { |
65 // Returns 0 when the risk of impacting responsiveness is minimal. | 37 // Returns 0 when the risk of impacting responsiveness is minimal. |
66 // Returns 1 when it is maximal. | 38 // Returns 1 when it is maximal. |
67 // durationMs is the duration of a long idle task. | 39 // durationMs is the duration of a long task. |
68 // It is at least DEFAULT_LONG_TASK_MS. | 40 // It is at least LONG_TASK_MS. |
69 // The FAST_RESPONSE_HISTOGRAM was designed to permit both a 50ms idle task | 41 // The FAST_RESPONSE_HISTOGRAM was designed to permit both a 50ms task |
70 // when a Scroll Response begins, plus 16ms latency between the idle task | 42 // when a Scroll Response begins, plus 16ms latency between the task |
71 // and the first frame of the scroll, without impacting the responsiveness | 43 // and the first frame of the scroll, without impacting the responsiveness |
72 // score. | 44 // score. |
73 // Add 16ms to durationMs to simulate the standard (maximum ideal) scroll | 45 // Add 16ms to durationMs to simulate the standard (maximum ideal) scroll |
74 // response latency, and use the FAST_RESPONSE_HISTOGRAM to punish every ms | 46 // response latency, and use the FAST_RESPONSE_HISTOGRAM to punish every ms |
75 // that the long idle task exceeds DEFAULT_LONG_TASK_MS. | 47 // that the long task exceeds LONG_TASK_MS. |
76 | 48 |
77 durationMs += 16; | 49 durationMs += 16; |
78 | 50 |
79 // computeDurationResponsiveness returns a normalized percentage that | 51 // This returns a normalized percentage that |
80 // represents the fraction of users that would be satisfied with a | 52 // represents the fraction of users that would be satisfied with a |
81 // Scroll Response that takes durationMs to respond. | 53 // Scroll Response that takes durationMs to respond. |
82 // The risk of impacting responsiveness is approximated as the long task's | 54 // The risk of impacting responsiveness is approximated as the long task's |
83 // impact on a hypothetical Scroll Response that starts when the long task | 55 // impact on a hypothetical Scroll Response that starts when the long task |
84 // starts, and then takes the standard 16ms to respond after the long task | 56 // starts, and then takes the standard 16ms to respond after the long task |
85 // finishes. | 57 // finishes. |
86 // We imagine a Scroll Response instead of a Load or another type of | 58 // We imagine a Scroll Response instead of a Load or another type of |
87 // Response because the Scroll Response carries the strictest expectation. | 59 // Response because the Scroll Response carries the strictest expectation. |
88 // The risk of impacting responsiveness is framed as the fraction of users | 60 // The risk of impacting responsiveness is framed as the fraction of users |
89 // that would be *un*satisifed with the responsiveness of that hypothetical | 61 // that would be *un*satisifed with the responsiveness of that hypothetical |
90 // Scroll Response. The fraction of users who are unsatisfied with something | 62 // Scroll Response. The fraction of users who are unsatisfied with something |
91 // is equal to 1 - the fraction of users who are satisfied with it. | 63 // is equal to 1 - the fraction of users who are satisfied with it. |
92 return 1 - computeDurationResponsiveness( | 64 return RESPONSE_RISK.computePercentile(durationMs / MS_PER_S); |
93 FAST_RESPONSE_HISTOGRAM, durationMs); | |
94 } | 65 } |
95 | 66 |
96 // This weighting function is similar to tr.metrics.sh.perceptualBlend, | 67 /** |
97 // but this version is appropriate for SmallerIsBetter metrics, whereas | 68 * This weighting function is similar to tr.metrics.sh.perceptualBlend, |
98 // that version is for BiggerIsBetter metrics. | 69 * but this version is appropriate for SmallerIsBetter metrics, whereas |
99 // (This would not be necessary if hazard were reframed as a BiggerIsBetter | 70 * that version is for BiggerIsBetter metrics. |
100 // metric such as "stability".) | 71 * (This would not be necessary if hazard were reframed as a BiggerIsBetter |
101 // Also, that version assumes that the 'ary' will be UserExpectations, whereas | 72 * metric such as "input readiness".) |
102 // this version assumes that the 'ary' will be scores. | 73 * Also, that version assumes that the 'ary' will be UserExpectations, whereas |
| 74 * this version assumes that the 'ary' will be scores. |
| 75 * |
| 76 * @param {number} hazardScore |
| 77 * @return {number} weight |
| 78 */ |
103 function perceptualBlendSmallerIsBetter(hazardScore) { | 79 function perceptualBlendSmallerIsBetter(hazardScore) { |
104 return Math.exp(hazardScore); | 80 return Math.exp(hazardScore); |
105 } | 81 } |
106 | 82 |
107 // This metric requires only the 'toplevel' tracing category, | 83 /** |
108 // in addition to whatever categories are required to compute the | 84 * Compute and return the normalized score for the risk that a speculative |
109 // IdleExpectations (or rather the R/A/Ls that delineate the Idles). | 85 * input's responsiveness would have been impacted by long tasks on the given |
110 // This metric computes a hazard score for each Idle independently (instead of | 86 * thread in the given range. |
111 // grouping all long idle tasks together) because each Idle is different: | 87 * |
112 // the Idle before a Load is usually very empty, whereas the Idle immediately | 88 * @param {tr.model.Thread} thread |
113 // after a Load is usually still very active, since Loads end at a very early | 89 * @param {tr.b.Range=} opt_range |
114 // end point (the first contentful paint) while many parts of the page are | 90 * @return {number} hazard |
115 // still loading. (There may not necessarily be an Idle after a Load in | 91 */ |
116 // real-world traces, but there almost always is in telemetry.) | 92 function computeHazardForLongTasksInRangeOnThread(thread, opt_range) { |
117 function computeLongIdleTaskHazard(hazardScores, ue) { | 93 var taskHazardScores = []; |
118 var longTaskScores = []; | 94 tr.metrics.sh.iterateLongTopLevelTasksOnThreadInRange( |
119 | 95 thread, opt_range, function(task) { |
120 findLongTasks(ue).forEach(function(longTask) { | 96 taskHazardScores.push(computeResponsivenessRisk(task.duration)); |
121 longTaskScores.push(computeResponsivenessRisk(longTask.duration)); | |
122 }); | 97 }); |
123 | 98 return tr.b.Statistics.weightedMean( |
124 var options = {description: 'Risk of impacting responsiveness'}; | 99 taskHazardScores, perceptualBlendSmallerIsBetter); |
125 | |
126 var hazardScore = tr.b.Statistics.weightedMean( | |
127 longTaskScores, perceptualBlendSmallerIsBetter); | |
128 | |
129 if (hazardScore === undefined) | |
130 hazardScore = 0; | |
131 | |
132 hazardScores.push(hazardScore); | |
133 } | 100 } |
134 | 101 |
| 102 /** |
| 103 * Compute and return the normalized score for the risk that a speculative |
| 104 * input's responsiveness would have been impacted by long tasks. |
| 105 * |
| 106 * @param {tr.model.Model} model The model. |
| 107 * @return {number} hazard |
| 108 */ |
| 109 function computeHazardForLongTasks(model) { |
| 110 var threadHazardScores = []; |
| 111 tr.metrics.sh.iterateRendererMainThreads(model, function(thread) { |
| 112 threadHazardScores.push(computeHazardForLongTasksInRangeOnThread( |
| 113 thread)); |
| 114 }); |
| 115 return tr.b.Statistics.weightedMean( |
| 116 threadHazardScores, perceptualBlendSmallerIsBetter); |
| 117 } |
| 118 |
| 119 /** |
| 120 * This EXPERIMENTAL metric computes a scalar normalized score that |
| 121 * represents the risk that a speculative input's responsiveness would have |
| 122 * been impacted by long tasks. |
| 123 * This metric requires only the 'toplevel' tracing category. |
| 124 */ |
135 function hazardMetric(values, model) { | 125 function hazardMetric(values, model) { |
136 var hazardScores = []; | 126 var overallHazard = computeHazardForLongTasks(model); |
137 | |
138 model.userModel.expectations.forEach(function(ue) { | |
139 // Add normalized metrics to diagnostics.values. | |
140 // TODO(memory): Add memory here. | |
141 | |
142 if (ue instanceof tr.model.um.IdleExpectation) | |
143 computeLongIdleTaskHazard(hazardScores, ue); | |
144 }); | |
145 | |
146 var options = {description: 'Risk of impacting responsiveness'}; | |
147 | |
148 var overallHazard = tr.b.Statistics.weightedMean( | |
149 hazardScores, perceptualBlendSmallerIsBetter); | |
150 | |
151 if (overallHazard === undefined) | 127 if (overallHazard === undefined) |
152 overallHazard = 0; | 128 overallHazard = 0; |
153 | 129 |
154 values.addValue(new tr.v.NumericValue( | 130 values.addValue(new tr.v.NumericValue( |
155 'hazard', new tr.v.ScalarNumeric( | 131 'hazard', new tr.v.ScalarNumeric( |
156 normalizedPercentage_smallerIsBetter, overallHazard), | 132 normalizedPercentage_smallerIsBetter, overallHazard))); |
157 options)); | |
158 } | 133 } |
159 | 134 |
160 tr.metrics.MetricRegistry.register(hazardMetric); | 135 tr.metrics.MetricRegistry.register(hazardMetric); |
161 | 136 |
162 return { | 137 return { |
163 hazardMetric: hazardMetric, | 138 hazardMetric: hazardMetric, |
164 computeLongIdleTaskHazard: computeLongIdleTaskHazard | 139 computeHazardForLongTasksInRangeOnThread: |
| 140 computeHazardForLongTasksInRangeOnThread, |
| 141 computeHazardForLongTasks: computeHazardForLongTasks, |
| 142 computeResponsivenessRisk: computeResponsivenessRisk |
165 }; | 143 }; |
166 }); | 144 }); |
167 </script> | 145 </script> |
OLD | NEW |