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

Side by Side Diff: tracing/tracing/metrics/webrtc/webrtc_rendering_metric.html

Issue 2711623002: Add a TBMv2 webrtc_rendering_metric. (Closed)
Patch Set: Addressed comments. Started writing tests. Created 3 years, 9 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
OLDNEW
(Empty)
1 <!DOCTYPE html>
2 <!--
3 Copyright 2017 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/range.html">
9 <link rel="import" href="/tracing/base/unit.html">
10 <link rel="import" href="/tracing/base/utils.html">
11 <link rel="import" href="/tracing/metrics/metric_registry.html">
12 <link rel="import" href="/tracing/metrics/v8/utils.html">
13 <link rel="import" href="/tracing/value/histogram.html">
14
15 <script>
16 'use strict';
17
18 tr.exportTo('tr.metrics.webrtc', function() {
19 const DISPLAY_HERTZ = 60.0;
20 const VSYNC_DURATION_US = 1e6 / DISPLAY_HERTZ;
21 // When to consider a frame frozen (in VSYNC units) meaning 1 initial
22 // frame + 5 repeats of that frame.
23 const FROZEN_FRAME_VSYNC_COUNT_THRESHOLD = 6;
24 // How much more severe is a 'Badly out of sync' render event compared to an
25 // 'Out of sync' one when calculating the smoothness score.
26 const SEVERITY = 3;
27
28 const WEB_MEDIA_PLAYER_UPDATE_TITLE = 'WebMediaPlayerMS::UpdateCurrentFrame';
29 // These four are args for WebMediaPlayerMS update events.
30 const IDEAL_RENDER_INSTANT_NAME = 'Ideal Render Instant';
31 const ACTUAL_RENDER_BEGIN_NAME = 'Actual Render Begin';
32 const ACTUAL_RENDER_END_NAME = 'Actual Render End';
33 // The events of interest have a 'Serial' argument which represents the
34 // stream ID.
35 const STREAM_ID_NAME = 'Serial';
36
37 const REQUIRED_EVENT_ARGS_NAMES = [
38 IDEAL_RENDER_INSTANT_NAME, ACTUAL_RENDER_BEGIN_NAME, ACTUAL_RENDER_END_NAME,
39 STREAM_ID_NAME
40 ];
41
42 const percentage_biggerIsBetter =
43 tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;
44 const percentage_smallerIsBetter =
45 tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;
46 const timeDurationInMs_smallerIsBetter =
47 tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
48 const unitlessNumber_biggerIsBetter =
49 tr.b.Unit.byName.unitlessNumber_biggerIsBetter;
50
51 /*
52 * Verify that the event is a valid event.
53 *
54 * An event is valid if it is a WebMediaPlayerMS::UpdateCurrentFrame event,
55 * and has all of the mandatory arguments. See MANDATORY above.
56 */
57 function isValidEvent(event) {
58 if (event.title !== WEB_MEDIRA_PLAYER_UPDATE_TITLE || !event.args) {
59 return false;
60 }
61 for (let parameter of REQUIRED_EVENT_ARGS_NAMES) {
62 if (!(parameter in event.args)) {
63 return false;
64 }
65 }
66 return true;
67 }
68
69 function webrtcRenderingMetric(values, model) {
70 tr.metrics.v8.utils.groupAndProcessEvents(model,
71 isValidEvent,
72 event => event.args[STREAM_ID_NAME],
73 eventStream,
74 (streamName, events) => getTimeStats(values, streamName, events)
75 );
76 }
77
78 tr.metrics.MetricRegistry.register(webrtcRenderingMetric);
79
80 function addHistogram(scalarValues, values, name, unit) {
81 let histogram = new tr.v.Histogram(name, unit);
82 for (let scalarValue of scalarValues) {
83 histogram.addSample(scalarValue);
84 }
85 values.addHistogram(histogram);
86 }
87
88 function getTimeStats(values, streamName, events) {
89 let frameDistribution = getSourceToOutputDistribution(events);
90 addFpsFromCadence(values, frameDistribution);
91 addFreezeScore(values, frameDistribution);
92 let driftTimeStats = getDriftTimeStats(events);
93 addHistogram(driftTimeStats.driftTime, values,
94 'WebRTCRendering_drift_time', timeDurationInMs_smallerIsBetter);
95 addHistogram([driftTimeStats.renderingLengthError], values,
96 'WebRTCRendering_rendering_length_error', percentage_smallerIsBetter);
97 let smoothnessStats = getSmoothnessStats(driftTimeStats.driftTime);
98 addHistogram([smoothnessStats.percentBadlyOutOfSync], values,
99 'WebRTCRendering_percent_badly_out_of_sync',
100 percentage_smallerIsBetter);
101 addHistogram([smoothnessStats.percentOutOfSync], values,
102 'WebRTCRendering_percent_out_of_sync', percentage_smallerIsBetter);
103 addHistogram([smoothnessStats.smoothnessScore], values,
104 'WebRTCRendering_smoothness_score', percentage_biggerIsBetter);
105 }
106
107 /**
108 * Create the source to output distribution.
109 *
110 * If the overall display distribution is A1:A2:..:An, this will tell how
111 * many times a frame stays displayed during Ak*VSYNC_DURATION_US, also known
112 * as 'source to output' distribution.
113 *
114 * In other terms, a distribution B where
115 * B[k] = number of frames that are displayed k times.
116 *
117 * @param {Array.<event>} cadence - An array of events.
118 * @returns {Map} frameDistribution - The source to output distribution.
119 */
120 function getSourceToOutputDistribution(events) {
121 let cadence = tr.b.runLengthEncoding(
122 events.map(e => e.args[IDEAL_RENDER_INSTANT]));
123 let frameDistribution = new Map();
124 for (let ticks of cadence) {
125 frameDistribution.set(ticks.count,
126 1 + (frameDistribution.get(ticks.count) || 0));
127 }
128 return frameDistribution;
129 }
130
131 /**
132 * Calculate the apparent FPS from frame distribution.
133 *
134 * Knowing the display frequency and the frame distribution, it is possible to
135 * calculate the video apparent frame rate as played by WebMediaPlayerMs
136 * module.
137 *
138 * @param values
139 * @param {Map} frameDistribution - See getSourceToOutputDistribution.
140 */
141 function addFpsFromCadence(values, frameDistribution) {
142 let numberFrames = 0;
143 let numberVsyncs = 0;
144 for (let [ticks, count] of frameDistribution) {
145 numberFrames += count;
146 numberVsyncs += ticks * count;
147 }
148 let meanRatio = numberVsyncs / numberFrames;
149 addHistogram([DISPLAY_HERTZ / meanRatio], values, 'WebRTCRendering_fps',
150 unitlessNumber_biggerIsBetter);
151 }
152
153 /**
154 * Find evidence of frozen frames in distribution.
155 *
156 * For simplicity we count as freezing the frames that are repeated at least
157 * five times in a row counted from 'Ideal Render Instant' perspective.
158 * So let's say for 1 source frame, we rendered 6 frames, then we consider 5
159 * of these rendered frames as frozen.
160 * We mitigate this by saying anything unde 5 frozen frames will not be
161 * counted as frozen.
162 *
163 * In other terms, if C is the source to output distribution, the result is a
164 * distribution B where:
165 * B[k] = C[k+1] for each k in C greater than the frozen frame threshold (6).
166 *
167 * @param {Map} frameDistribution - See getSourceToOutputDistribution.
168 * @return {Map} frozenFrames - The frozen frames distribution.
169 */
170 function getFrozenFramesReports(frameDistribution) {
171 frozenFrames = {};
172 for (let [ticks, count] of frameDistribution) {
173 if (ticks >= FROZEN_FRAME_VSYNC_COUNT_THRESHOLD) {
174 frozenFrames.set(ticks - 1, count);
175 }
176 }
177 return frozenFrames;
178 }
179
180 /**
181 * Returns the weighted penalty for a number of frozen frames.
182 *
183 * We count for frozen anything above 6 vsync display duration for the same
184 * 'Initial Render Instant', which is five frozen frames.
185 *
186 * @param {Number} numberFrozenFrames - The number of frozen frames.
187 * @return {Number} - The weight penalty for the number of frozen frames.
188 */
189 function frozenPenaltyWeight(numberFrozenFrames) {
190 const penalty = {
191 0: 0,
192 1: 0,
193 2: 0,
194 3: 0,
195 4: 0,
196 5: 1,
197 6: 5,
198 7: 15,
199 8: 25
200 };
201 return penalty[numberFrozenFrames] || (8 * (numberFrozenFrames - 4));
202 }
203
204 /**
205 * Adds the freezing score.
206 *
207 * @param values
208 * @param {Map} frameDistribution - See getSourceToOutputDistribution.
209 */
210 function addFreezingScore(values, frameDistribution) {
211 let numberVsyncs = 0;
212 for (let [ticks, count] of frameDistribution) {
213 numberVsyncs += ticks * count;
214 }
215 let frozenFrames = getFrozenFramesReports(frameDistribution);
216 let freezingScore = 100;
217 for (let [ticks, count] of frozenFrames) {
218 let weight = frozenPenaltyWeight(ticks);
219 freezingScore -= 100 * count / numberVsyncs * weight;
220 if (freezingScore < 0) {
221 freezingScore = 0;
222 }
223 }
224 addHistogram([freezingScore], values, 'WebRTCRendering_freezing_score',
225 percentage_biggerIsBetter);
226 }
227
228 /**
229 * Get the drift time statistics.
230 *
231 * This method will calculate:
232 * - driftTime = list(actual render begin - ideal render).
233 * - renderingLengthError
234 *
235 * @param {Array.<Number>} events - An array of events.
236 * @returns {Object} - The driftTime and renderingLengthError calculated
237 * from the cadence.
238 */
239 function getDriftStats(events) {
240 let driftTime = [];
241 let discrepancy = [];
242 let oldIdealRender = 0;
243 let expectedIdealRender = 0;
244
245 for (let event of events) {
246 let currentIdealRender = event.args[IDEAL_RENDER_INSTANT_NAME];
247 // The expected time of the next 'Ideal Render' event begins as the
248 // current 'Ideal Render' time and increases by VSYNC_DURATION_US on every
249 // frame.
250 expectedIdealRender += VSYNC_DURATION_US;
251 if (currentIdealRender === oldIdealRender) {
252 continue;
253 }
254 let actualRenderBegin = event.args[ACTUAL_RENDER_BEGIN_NAME];
255 // When was the frame rendered vs. when it would've been ideal.
256 driftTime.push(actualRenderBegin - currentIdealRender);
257 // The discrepancy is the absolute difference between the current Ideal
258 // Render and the expected Ideal Render.
259 discrepancy.push(Math.abs(currentIdealRender - expectedIdealRender));
260 expectedIdealRender = currentIdealRender;
261 oldIdealRender = currentIdealRender;
262 }
263
264 let discrepancySum = tr.b.Statistics.sum(discrepancy) - discrepancy[0];
265 let lastIdealRender =
266 events[events.length - 1].args[IDEAL_RENDER_INSTANT_NAME];
267 let firstIdealRender = events[0].args[IDEAL_RENDER_INSTANT_NAME];
268 let idealRenderSpan = lastIdealRender - firstIdealRender;
269
270 let renderingLengthError = 100 * discrepancySum / idealRenderSpan;
271
272 return {
273 driftTime: driftTime,
274 renderingLengthError: renderingLengthError,
275 };
276 }
277
278 /**
279 * Get the smoothness stats from the normalized drift time.
280 *
281 * This method will calculate the smoothness score, along with the percentage
282 * of frames badly out of sync and the percentage of frames out of sync.
283 * To be considered badly out of sync, a frame has to have missed rendering by
284 * at least 2 * VSYNC_DURATION_US.
285 * To be considered out of sync, a frame has to have missed rendering by at
286 * least one VSYNC_DURATION_US.
287 * The smoothness score is a measure of how out of sync the frames are.
288 *
289 * @param {Array.<Number>} driftTimes - See getDriftStats.
290 * @returns {Object} - The percentBadlyOutOfSync, percentOutOfSync and
291 * smoothnesScore calculated from the driftTimes.
292 */
293 function getSmoothnessStats(driftTimes) {
294 let meanDriftTime = 0;
295 for (let driftTime of driftTimes) {
296 meanDriftTime += driftTime;
297 }
298 meanDriftTime /= driftTimes.length;
299 let normDriftTimes = [];
300 for (let driftTime of driftTimes) {
301 normDriftTimes.push(Math.abs(driftTime - meanDriftTime));
302 }
303
304 // How many times is a frame later/earlier than T=2*VSYNC_DURATION_US. Time
305 // is in microseconds
306 let framesSeverelyOutOfSync = 0;
307 // How many times is a frame later/earlier than VSYNC_DURATION_US.
308 let framesOutOfSync = 0;
309 for (let driftTime of normDriftTimes) {
310 if (Math.abs(driftTime) > VSYNC_DURATION_US) {
311 framesOutOfSync += 1;
312 }
313 if (Math.abs(driftTime) > 2 * VSYNC_DURATION_US) {
314 framesSeverelyOutOfSync += 1;
315 }
316 }
317 let percentBadlyOutOfSync = 100 * framesSeverelyOutOfSync /
318 driftTimes.length;
319 let percentOutOfSync = 100 * framesOutOfSync / driftTimes.length;
320
321 let framesOutOfSyncOnlyOnce = framesOutOfSync - framesSeverelyOutOfSync;
322
323 // Calculate smoothness metric. From the formula, we can see that smoothness
324 // score can be negative.
325 let smoothnessScore = 100 - 100 * (framesOutOfSyncOnlyOnce +
326 SEVERITY * framesSeverelyOutOfSync) / driftTimes.length;
327
328 // Minimum smoothness_score value allowed is zero.
329 if (smoothnessScore < 0) {
330 smoothnessScore = 0;
331 }
332
333 return {
334 percentBadlyOutOfSync: percentBadlyOutOfSync,
335 percentOutOfSync: percentOutOfSync,
336 smoothnessScore: smoothnessScore,
337 };
338 }
339
340 return {
341 webrtcRenderingMetric,
342 };
343 });
344 </script>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698