Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(69)

Side by Side Diff: tracing/tracing/metrics/estimated_input_latency_metric.html

Issue 2323533003: [Not for landing - CL being split] Add Estimated Input Latency - EQT 90th Percentile definition
Patch Set: Delete useless empty loop Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tracing/tracing/metrics/all_metrics.html ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 <!DOCTYPE html>
2 <!--
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
5 found in the LICENSE file.
6 -->
7
8 <link rel="import" href="/tracing/base/category_util.html">
9 <link rel="import" href="/tracing/metrics/metric_registry.html">
10 <link rel="import" href="/tracing/metrics/system_health/utils.html">
11 <link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
12 <link rel="import" href="/tracing/model/user_model/load_expectation.html">
13 <link rel="import" href="/tracing/value/histogram.html">
14 <link rel="import" href="/tracing/value/value.html">
15
16 <script>
17 'use strict';
18
19 tr.exportTo('tr.metrics', function() {
20
21 var TOPLEVEL_CATEGORY_NAME = 'toplevel';
22
23 // TODO(dproy): Figure out if this is really necessary
24 var BASE_RESPONSE_LATENCY = 0;
25
26 // TODO(dproy): This is copy pasta from loading_metric.html. Factor out.
benjhayden 2016/09/08 16:03:26 https://github.com/catapult-project/catapult/issue
dproy 2016/09/08 19:45:53 Ok I'm changing the TODO here to wait until that i
27 function hasCategoryAndName(event, category, title) {
28 return event.title === title && event.category &&
29 tr.b.getCategoryParts(event.category).indexOf(category) !== -1;
30 }
31
32 // TODO (dproy): This is copy pasta from loading_metric.html. Factor out.
benjhayden 2016/09/08 16:03:26 Feel free to move it to ChromeBrowserHelper.
dproy 2016/09/08 19:45:53 Done. I moved it to ChromeModelHelper because it f
33 function findTargetRendererHelper(chromeHelper) {
34 var largestPid = -1;
35 for (var pid in chromeHelper.rendererHelpers) {
36 var rendererHelper = chromeHelper.rendererHelpers[pid];
37 if (rendererHelper.isChromeTracingUI)
38 continue;
39 if (pid > largestPid)
40 largestPid = pid;
41 }
42
43 if (largestPid === -1)
44 return undefined;
45
46 return chromeHelper.rendererHelpers[largestPid];
47 }
48
49 function runConditionallyOnInnermostDescendants(slice, predicate, cb) {
50 var succeededOnSomeDescendant = false;
51 for (var child of slice.subSlices) {
52 var succeededOnThisChild = runConditionallyOnInnermostDescendants(
53 child, predicate, cb);
54 succeededOnSomeDescendant =
55 succeededOnThisChild || succeededOnSomeDescendant;
56 }
57
58 if (succeededOnSomeDescendant) return true;
benjhayden 2016/09/08 16:03:26 Why not move this early return up into the loop so
dproy 2016/09/08 19:45:53 I actually want it to run on all siblings. The pri
59
60 if (predicate(slice)) {
61 cb(slice);
62 return true;
63 } else {
64 return false;
65 }
66 }
67
68 function isTopLevelSlice(slice) {
benjhayden 2016/09/08 16:03:26 This name is confusing. Slice and AsyncSlice have
dproy 2016/09/08 19:45:53 Done.
69 return tr.b.getCategoryParts(slice.category)
70 .includes(TOPLEVEL_CATEGORY_NAME);
71 }
72
73 function forEachInnermostTopLevelSlices(thread, cb) {
74 for (var slice of thread.sliceGroup.topLevelSlices) {
benjhayden 2016/09/08 16:03:26 I seem to recall the existence of tracing categori
dproy 2016/09/08 19:45:53 That should be ok. I actually want the biggest and
75 runConditionallyOnInnermostDescendants(slice, isTopLevelSlice, cb);
76 }
77 }
78
79 function getAllInteractiveTimestampsSorted(model) {
80 // TODO(dproy): When LoadExpectation v.1.0 is released,
benjhayden 2016/09/08 16:03:26 I'll take care of this refactor. You can either ch
dproy 2016/09/08 19:45:53 Ok I'll remove the TODO for me here. Thanks!
81 // update this function to use the new LoadExpectation rather
82 // than calling loading_metric.html.
83
84 var values = new tr.v.ValueSet();
85 tr.metrics.sh.loadingMetric(values, model);
86 var ttiValues = values.getValuesNamed('timeToFirstInteractive');
87 var interactiveTsList = [];
88 for (var bin of tr.b.getOnlyElement(ttiValues).numeric.allBins)
89 for (var diagnostics of bin.diagnosticMaps) {
90 var breakdown = diagnostics.get('breakdown');
91 interactiveTsList.push(breakdown.value.interactive);
92 }
93 return interactiveTsList.sort((x, y) => x - y);
94 }
95
96 function getAllNavStartTimesSorted(rendererHelper) {
97 var list = [];
98 for (var ev of rendererHelper.mainThread.sliceGroup.childEvents()) {
99 if (!hasCategoryAndName(ev, 'blink.user_timing', 'navigationStart'))
100 continue;
101 list.push(ev.start);
102 }
103 return list.sort((x, y) => x - y);
104 }
105
106 // A task window is defined as time from TTI until either
107 // 1. beginning of next navigationStart event, or
108 // 2. end of traces
109 function getTaskWindows(interactiveTsList, navStartTimeList, endOfTraces) {
110 var curNavStartTimeIndex = 0;
111 var endOfLastWindow = -Infinity;
112 var taskWindows = [];
113 for (var curTTI of interactiveTsList) {
114 var curNavStartTime = navStartTimeList[curNavStartTimeIndex];
115 while (curNavStartTime !== undefined && curNavStartTime < curTTI) {
116 // There are possibly multiple navigationStart timestamps between
117 // two interactive timestamps - the previous page load could
118 // never reach interactive status.
119 curNavStartTimeIndex++;
120 curNavStartTime = navStartTimeList[curNavStartTimeIndex];
121 }
122
123 if (curNavStartTime === endOfLastWindow) {
124 // This is a violation of core assumption.
125 // TODO: When two pages share a render process, we can possibly
126 // have two interactive time stamps between two navigation events.
127 // If both interactive timestamps are reported, it is not clear how
128 // to define estimated input latency.
129 throw new Error("Two TTI timestamps with no navigation between them");
130 }
131
132 if (curNavStartTime === undefined) {
133 taskWindows.push({start: curTTI, end: endOfTraces});
134 endOfLastWindow = endOfTraces;
135 continue;
136 }
137
138 taskWindows.push({start: curTTI, end: curNavStartTime});
139 endOfLastWindow = curNavStartTime;
140 }
141 return taskWindows;
142 }
143
144 /**
145 * Note: Taken from
146 * https://github.com/GoogleChrome/lighthouse/blob/a5bbe2338fa474c94bb87584940 8704c81fec3df/lighthouse-core/lib/traces/tracing-processor.js#L121
147 *
148 * Calculate duration at specified percentiles for given population of
149 * durations.
150 * If one of the durations overlaps the end of the window, the full
151 * duration should be in the duration array, but the length not included
152 * within the window should be given as `clippedLength`. For instance, if a
153 * 50ms duration occurs 10ms before the end of the window, `50` should be in
154 * the `durations` array, and `clippedLength` should be set to 40.
155 * @see https://docs.google.com/document/d/18gvP-CBA2BiBpi3Rz1I1ISciKGhniTSZ9T Y0XCnXS7E/preview
156 */
157 function calculateRiskPercentiles(
158 durations, totalTime, percentiles, clippedLength) {
159 clippedLength = clippedLength || 0;
160
161 var busyTime = 0;
162 for (var i = 0; i < durations.length; i++) {
163 busyTime += durations[i];
164
165 }
166 busyTime -= clippedLength;
167
168 // Start with idle time already compvare.
169 var compvaredTime = totalTime - busyTime;
170 var duration = 0;
171 var cdfTime = compvaredTime;
172 var results = [];
173
174 var durationIndex = -1;
175 var remainingCount = durations.length + 1;
176 if (clippedLength > 0) {
177 // If there was a clipped duration, one less in count
178 // since one hasn't started yet.
179 remainingCount--;
180
181 }
182
183 // Find percentiles of interest, in order.
184 for (var percentile of percentiles) {
185 // Loop over durations, calculating a CDF value for each until it is above
186 // the target percentile.
187 var percentivarime = percentile * totalTime;
188 while (cdfTime < percentivarime && durationIndex < durations.length - 1) {
189 compvaredTime += duration;
190 remainingCount -= (duration < 0 ? -1 : 1);
191
192 if (clippedLength > 0 && clippedLength < durations[durationIndex + 1]) {
193 duration = -clippedLength;
194 clippedLength = 0;
195 } else {
196 durationIndex++;
197 duration = durations[durationIndex];
198 }
199
200 // Calculate value of CDF (multiplied by totalTime) for the end of
201 // this duration.
202 cdfTime = compvaredTime + Math.abs(duration) * remainingCount;
203 }
204
205 // Negative results are within idle time (0ms wait by definition),
206 // so clamp at zero.
207 results.push({
208 percentile,
209 time: Math.max(0, (percentivarime - compvaredTime) / remainingCount) +
210 BASE_RESPONSE_LATENCY
211 });
212 }
213
214 return results;
215 }
216
217 /**
218 * Note: This is adapted from
219 * https://github.com/GoogleChrome/lighthouse/blob/a5bbe2338fa474c94bb87584940 8704c81fec3df/lighthouse-core/lib/traces/tracing-processor.js#L185
220 */
221 function getEQTPercentilesForWindow(
222 percentiles, rendererHelper, startTime, endTime) {
223 var totalTime = endTime - startTime;
224 percentiles.sort((a, b) => a - b);
225
226 var durations = [];
227 var clippedLength = 0;
228 forEachInnermostTopLevelSlices(rendererHelper.mainThread, slice => {
229 // Discard slices outside range.
230 if (slice.end <= startTime || slice.start >= endTime) {
231 return;
232 }
233
234 // Clip any at edges of range.
235 let duration = slice.duration;
236 let sliceStart = slice.start;
237 if (sliceStart < startTime) {
238 // Any part of task before window can be discarded.
239 sliceStart = startTime;
240 duration = slice.end - sliceStart;
241
242 }
243 if (slice.end > endTime) {
244 // Any part of task after window must be clipped but accounted for.
245 clippedLength = duration - (endTime - sliceStart);
246 }
247 durations.push(duration);
248
249 });
250 durations.sort((a, b) => a - b);
251 return calculateRiskPercentiles(
252 durations, totalTime, percentiles, clippedLength);
253 }
254
255 /**
256 * @param {!tr.v.ValueSet} values
257 * @param {!tr.model.Model} model
258 * @param {!Object=} opt_options
259 */
260 function estimatedInputLatencyMetric(values, model, opt_options) {
261
262 var chromeHelper = model.getOrCreateHelper(
263 tr.model.helpers.ChromeModelHelper);
264 var rendererHelper = findTargetRendererHelper(chromeHelper);
265
266 var interactiveTimestamps = getAllInteractiveTimestampsSorted(model);
267
268 // We're assuming children iframes will never emit navigationStart events
269 var navStartTimeList = getAllNavStartTimesSorted(rendererHelper);
270 var taskWindowList = getTaskWindows(
271 interactiveTimestamps,
272 navStartTimeList,
273 rendererHelper.mainThread.bounds.max);
274
275 var eqtTargetPercentiles = [.9];
276
277 var EQT90thPercentile = new tr.v.Histogram(
278 tr.v.Unit.byName.timeDurationInMs_smallerIsBetter,
279 tr.v.HistogramBinBoundaries.createExponential(0.1, 1e3, 100));
280
281 for (var taskWindow of taskWindowList) {
282 var eqtPercentiles = getEQTPercentilesForWindow(
283 eqtTargetPercentiles, rendererHelper, taskWindow.start, taskWindow.end);
284 var filtered = eqtPercentiles.filter(res => res.percentile === 0.9)
285 var EQT90th = eqtPercentiles.filter(res => res.percentile === 0.9)[0];
286 if (EQT90th !== undefined) EQT90thPercentile.addSample(EQT90th.time);
287 }
288
289 values.addValue(new tr.v.NumericValue(
290 'Expected Queueing Time 90th Percentile', EQT90thPercentile,
291 { description: '90th percetile of expected queueing time for ' +
292 'uniformly distributed random input event after TTI' }));
293 }
294
295 tr.metrics.MetricRegistry.register(estimatedInputLatencyMetric, {
296 supportsRangeOfInterest: false
297 });
298
299 return {
300 estimatedInputLatencyMetric: estimatedInputLatencyMetric,
301 };
302 });
303 </script>
OLDNEW
« no previous file with comments | « tracing/tracing/metrics/all_metrics.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698