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

Unified Diff: tracing/tracing/metrics/webrtc/webrtc_rendering_timeline.html

Issue 2711623002: Add a TBMv2 webrtc_rendering_metric. (Closed)
Patch Set: Address some comments. Created 3 years, 10 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 side-by-side diff with in-line comments
Download patch
Index: tracing/tracing/metrics/webrtc/webrtc_rendering_timeline.html
diff --git a/tracing/tracing/metrics/webrtc/webrtc_rendering_timeline.html b/tracing/tracing/metrics/webrtc/webrtc_rendering_timeline.html
new file mode 100644
index 0000000000000000000000000000000000000000..5ee9533bc63332263a20bbfbe4aed893cb27acd9
--- /dev/null
+++ b/tracing/tracing/metrics/webrtc/webrtc_rendering_timeline.html
@@ -0,0 +1,373 @@
+<!DOCTYPE html>
benjhayden 2017/02/22 21:38:53 Apologies if somebody else has already mentioned i
ehmaldonado_chromium 2017/02/23 00:17:32 Renamed to webrtcRendering -> webrtc_rendering
+<!--
+Copyright 2017 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/range.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/metrics/metric_registry.html">
+<link rel="import" href="/tracing/metrics/v8/utils.html">
+<link rel="import" href="/tracing/value/histogram.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.metrics.webrtc', function() {
+ const DISPLAY_HERTZ = 60.0;
+ const VSYNC_DURATION = 1e6 / DISPLAY_HERTZ;
benjhayden 2017/02/22 21:35:03 Can you rename this VSYNC_DURATION_US to clarify t
ehmaldonado_chromium 2017/02/23 00:17:32 Done.
+ // When to consider a frame frozen (in VSYNC units): meaning 1 initial
+ // frame + 5 repeats of that frame.
+ const FROZEN_THRESHOLD = 6;
benjhayden 2017/02/22 21:35:03 A better name for this might be "FROZEN_FRAME_VSYN
ehmaldonado_chromium 2017/02/23 00:17:32 Done.
+ // Severity factor.
tdresser 2017/02/22 20:08:18 This comment doesn't really add anything. What's t
ehmaldonado_chromium 2017/02/23 00:17:32 Removed it. It's how much a badly out of sync is w
+ const SEVERITY = 3;
+
+ const WEB_MEDIA_PLAYER_MS_EVENT = 'WebMediaPlayerMS::UpdateCurrentFrame';
benjhayden 2017/02/22 21:35:03 "MS" usually means milliseconds in tracing, and th
+ const IDEAL_RENDER_INSTANT = 'Ideal Render Instant';
benjhayden 2017/02/22 21:35:03 Please add "_NAME" to these constants and add comm
+ const ACTUAL_RENDER_BEGIN = 'Actual Render Begin';
+ const ACTUAL_RENDER_END = 'Actual Render End';
+ const SERIAL = 'Serial';
benjhayden 2017/02/22 21:35:03 Can you rename this to "STREAM_ID_NAME"?
+
+ const MANDATORY = [
benjhayden 2017/02/22 21:35:03 Maybe "REQUIRED_EVENT_ARGS_NAMES"?
+ IDEAL_RENDER_INSTANT, ACTUAL_RENDER_BEGIN, ACTUAL_RENDER_END, SERIAL
+ ];
+
+ const percentage_biggerIsBetter =
+ tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;
+ const percentage_smallerIsBetter =
+ tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;
+ const timeDurationInMs_smallerIsBetter =
+ tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
+ const unitlessNumber_biggerIsBetter =
+ tr.b.Unit.byName.unitlessNumber_biggerIsBetter;
+
+ /**
+ * Extract the 'Serial' argument from the event.
+ *
+ * The events of interest have a 'Serial' argument which represents the
+ * stream ID.
+ */
+ function eventStream(event) {
benjhayden 2017/02/22 21:35:03 This can also be an arrow function in its single c
ehmaldonado_chromium 2017/02/23 00:17:32 Done.
+ return event.args[SERIAL];
+ }
+
+ /**
benjhayden 2017/02/22 21:35:03 If docstrings don't use jsdoc tags like @param and
ehmaldonado_chromium 2017/02/23 00:17:32 Done.
+ * Verify that the event is a valid event.
+ *
+ * An event is valid if it is a WebMediaPlayerMS::UpdateCurrentFrame event,
+ * and has all of the mandatory arguments. See MANDATORY above.
+ */
+ function isValidEvent(event) {
+ if (event.title !== WEB_MEDIA_PLAYER_MS_EVENT || !event.args) {
+ return false;
+ }
+ for (let parameter of mandatory) {
benjhayden 2017/02/22 21:35:03 Did you mean "MANDATORY"?
ehmaldonado_chromium 2017/02/23 00:17:32 Done.
+ if (!(parameter in event.args)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function webRtcRenderingTimelineMetric(values, model) {
benjhayden 2017/02/22 21:35:03 This |values| is a HistogramSet fka ValueSet. Plea
+ tr.metrics.v8.utils.groupAndProcessEvents(
+ model, isValidEvent, eventStream, getTimeStats.bind(null, values));
benjhayden 2017/02/22 21:11:47 Please don't use null in tracing. I think that an
+ }
+
+ tr.metrics.MetricRegistry.register(webRtcRenderingTimelineMetric);
+
+ function addScalarValue(scalarValue, values, name, unit) {
benjhayden 2017/02/22 21:35:03 This looks like a special case of addListOfScalarV
+ let histogram = new tr.v.Histogram(name, unit);
+ histogram.addSample(scalarValue);
+ values.addHistogram(histogram);
+ }
+
+ function addListOfScalarValues(scalarValues, values, name, unit) {
benjhayden 2017/02/22 21:35:03 ListOfScalarValues isn't a thing in TBMv2. Can you
+ let histogram = new tr.v.Histogram(name, unit);
+ for (let scalarValue of scalarValues) {
+ histogram.addSample(scalarValue);
+ }
+ values.addHistogram(histogram);
+ }
+
+ function getTimeStats(values, streamName, events) {
+ let cadence = getCadence(events);
+ let frameDistribution = getSourceToOutputDistribution(cadence);
+ addFpsFromCadence(values, frameDistribution);
+ addFreezeScore(values, frameDistribution);
+ let driftTimeStats = getDriftTimeStats(events, cadence);
+ addListOfScalarValues(driftTimeStats.driftTime, values,
+ 'WebRTCRendering_drift_time', timeDurationInMs_smallerIsBetter);
+ addScalarValue(driftTimeStats.renderingLengthError, values,
+ 'WebRTCRendering_rendering_length_error', percentage_smallerIsBetter);
+ let smoothnessStats = getSmoothnessStats(driftTimeStats.driftTime);
+ addScalarValue(smoothnessStats.percentBadlyOutOfSync, values,
+ 'WebRTCRendering_percent_badly_out_of_sync',
+ percentage_smallerIsBetter);
+ addScalarValue(smoothnessStats.percentOutOfSync, values,
+ 'WebRTCRendering_percent_out_of_sync', percentage_smallerIsBetter);
+ addScalarValue(smoothnessStats.smoothnessScore, values,
+ 'WebRTCRendering_smoothness_score', percentage_biggerIsBetter);
+ }
+
+ /**
+ * Calculate the apparent cadence of the rendering.
+ *
+ * In this paragraph we will be using regex notation.
+ * What is intended by the word cadence is a sort of extended instantaneous
+ * 'Cadence' (thus not necessarily periodic).
+ * Just as an example, a normal 'Cadence' could be something like [2 3] which
+ * means possibly an observed frame persistence progression of [{2 3}+] for an
+ * ideal 20FPS video source. So what we are calculating here is the list of
+ * frame persistence, kind of a 'Proto-Cadence', but cadence is shorter so we
+ * abuse the word.
+ *
+ * @param {Object} events - An array of events.
+ */
+ function getCadence(events) {
+ let cadence = [];
+ let framePersistence = 0;
+ let oldIdealRender = 0;
+ for (let event of events) {
+ if (event.args[IDEAL_RENDER_INSTANT] === oldIdealRender) {
+ framePersistence += 1;
+ } else {
+ cadence.push(framePersistence);
+ framePersistence = 1;
+ oldIdealRender = event.args[IDEAL_RENDER_INSTANT];
+ }
+ }
+ cadence.append(framePersistence);
+ cadence.shift();
+ return cadence;
+ }
+
+ /**
+ * Create distribution for the cadence frame display values.
+ *
+ * If the overall display distribution is A1:A2:..:An, this will tell how
+ * many times a frame stays displayed during Ak*VSYNC_DURATION, also known as
+ * 'source to output' distribution.
+ *
+ * In other terms, if C is the cadence, the result is a distribution B,
+ * where B[k] = number of times k appears in C, for each k in C.
+ *
+ * @param {Array} cadence - See getCadence above.
tdresser 2017/02/22 20:08:18 State what this is an array of (and below).
+ * @returns {Object} frameDistribution - The source to output distribution.
tdresser 2017/02/22 20:08:18 Isn't frameDistribution an array?
benjhayden 2017/02/22 21:35:03 It looks like a map from ticks (frame sequence len
+ */
+ function getSourceToOutputDistribution(cadence) {
+ let frameDistribution = {};
+ for (let ticks of cadence) {
+ frameDistribution[ticks] = frameDistribution[ticks] + 1 || 1;
+ }
+ return frameDistribution;
+ }
+
+ /**
+ * Calculate the apparent FPS from frame distribution.
+ *
+ * Knowing the display frequency and the frame distribution, it is possible to
+ * calculate the video apparent frame rate as played by WebMediaPlayerMs
+ * module.
+ *
+ * @param values
+ * @param frameDistribution - See getSourceToOutputDistribution.
+ */
+ function addFpsFromCadence(values, frameDistribution) {
+ let numberFrames = 0;
+ let numberVsyncs = 0;
+ for (let ticks of frameDistribution) {
benjhayden 2017/02/22 21:35:03 Ah, for..of doesn't work for objects. See my respo
+ numberFrames += frameDistribution[ticks];
+ numberVsyncs += ticks * frameDistribution[ticks];
+ }
+ let meanRatio = numberVsyncs / numberFrames;
+ addScalarValue(DISPLAY_HERTZ / meanRatio, values, 'WebRTCRendering_fps',
+ unitlessNumber_biggerIsBetter);
+ }
+
+ /**
+ * Find evidence of frozen frames in distribution.
+ *
+ * For simplicity we count as freezing the frames that appear at least five
tdresser 2017/02/22 20:08:18 Isn't it 6 times in a row?
+ * times in a row counted from 'Ideal Render Instant' perspective. So let's
+ * say for 1 source frame, we rendered 6 frames, then we consider 5 of these
+ * rendered frames as frozen. But we mitigate this by saying anything under
+ * 5 frozen frames will not be counted as frozen.
+ *
+ * In other terms, if C is the source to output distribution, the result is a
+ * distribution B where:
+ * B[k] = C[k+1] for each k in C greater that FROZEN_THRESHOLD
+ *
+ * @param {Object} frameDistribution - See getSourceToOutputDistribution.
+ * @return {Object} frozenFrames - The frozen frames distribution.
+ */
+ function getFrozenFramesReports(frameDistribution) {
+ frozenFrames = {};
+ for (let ticks of frameDistribution) {
+ if (ticks >= FROZEN_THRESHOLD) {
+ frozenFrames[ticks - 1] = frameDistribution[ticks];
+ }
+ }
+ return frozenFrames;
+ }
+
+ /**
+ * Returns the weighted penalty for a number of frozen frames.
+ *
+ * As mentioned earlier, we count for frozen anything above 6 vsync display
benjhayden 2017/02/22 21:35:03 Please delete "As mentioned earlier".
+ * duration for the same 'Initial Render Instant', which is five frozen
+ * frames.
+ *
+ * @param {Number} numberFrozenFrames - The number of frozen frames.
+ * @return {Number} - The weight penalty for the number of frozen frames.
+ */
+ function frozenPenaltyWeight(numberFrozenFrames) {
+ const penalty = {
+ 0: 0,
+ 1: 0,
+ 2: 0,
+ 3: 0,
+ 4: 0,
+ 5: 1,
+ 6: 5,
+ 7: 15,
+ 8: 25
+ };
+ return penalty[numberFrozenFrames] || 8 * (numberFrozenFrames - 4);
+ }
+
+ /**
+ * Adds the freezing score.
+ *
+ * @param values
+ * @param {Object} frameDistribution - See getSourceToOutputDistribution.
+ */
+ function addFreezingScore(values, frameDistribution) {
+ let numberVsyncs = 0;
+ for (let ticks of frameDistribution) {
+ numberVsyncs += ticks * frameDistribution[ticks];
+ }
+ let frozenFrames = getFrozenFramesReports(frameDistribution);
+ let freezingScore = 100;
+ for (let frozenReport of frozenFrames) {
+ let weight = frozenPenaltyWeight(frozenReport.frozenFrames);
+ freezingScore -= 100 * frozenReport.occurences / numberVsyncs * weight;
+ if (freezingScore < 0) {
+ freezingScore = 0;
+ }
+ }
+ addScalarValue(freezingScore, values, 'WebRTCRendering_freezing_score',
+ percentage_biggerIsBetter);
+ }
+
+ /**
+ * Get the drift time statistics.
+ *
+ * This method will calculate:
+ * - driftTime = list(actual render begin - ideal render).
+ * - renderingLengthError
+ *
+ * @param events
+ * @param {Array} cadence - See getCadence.
+ * @returns {Object} - The driftTime and renderingLengthError calculated
+ * from the cadence.
+ */
+ function getDriftStats(events, cadence) {
+ let driftTime = [];
+ let oldIdealRender = 0;
+ let discrepancy = [];
+ let discrepancySum = 0;
+ let index = 0;
+ for (let event of events) {
+ let currentIdealRender = event.args[IDEAL_RENDER_INSTANT];
+ if (currentIdealRender === oldIdealRender) {
+ continue;
+ }
+ let actualRenderBegin = event.args[ACTUAL_RENDER_BEGIN];
+ driftTime.push(actualRenderBegin - currentIdealRender);
+ discrepancy.push(Math.abs(currentIdealRender - oldIdealRender -
+ VSYNC_DURATION * cadence[index]));
+ discrepancySum += discrepancy[index];
+ oldIdealRender = currentIdealRender;
+ index += 1;
+ }
+ discrepancySum -= discrepancy[0];
+ let lastIdealRender = events[events.length - 1].args[IDEAL_RENDER_INSTANT];
+ let firstIdealRender = events[0].args[IDEAL_RENDER_INSTANT];
+ let renderingLengthError = 100 * discrepancySum / (lastIdealRender -
+ firstIdealRender);
+
+ return {
+ driftTime: driftTime,
+ renderingLengthError: renderingLengthError,
+ };
+ }
+
+ /**
+ * Get the smoothness stats from the normalized drift time.
+ *
+ * This method will calculate the smoothness score, along with the percentage
+ * of frames badly out of sync and the percentage of frames out of sync.
+ * To be considered badly out of sync, a frame has to have missed rendering by
+ * at least 2 * VSYNC_DURATION.
+ * To be considered out of sync, a frame has to have missed rendering by at
+ * least one VSYNC_DURATION.
+ * The smoothness score is a measure of how out of sync the frames are.
+ *
+ * @param {Array} driftTimes - See getDriftStats.
+ * @returns {Object} - The percentBadlyOutOfSync, percentOutOfSync and
+ * smoothnesScore calculated from the driftTimes.
+ */
+ function getSmoothnessStats(driftTimes) {
+ let meanDriftTime = 0;
+ for (let driftTime of driftTimes) {
+ meanDriftTime += driftTime;
+ }
+ meanDriftTime /= driftTimes.length;
+ let normDriftTimes = [];
+ for (let driftTime of driftTimes) {
+ normDriftTimes.push(Math.abs(driftTime - meanDriftTime));
+ }
+
+ // How many times is a frame later/earlier than T=2*VSYNC_DURATION. Time is
+ // in microseconds
+ let framesSeverelyOutOfSync = 0;
+ // How many times is a frame later/earlier than VSYNC_DURATION.
+ let framesOutOfSync = 0;
+ for (let driftTime of normDriftTimes) {
+ if (Math.abs(driftTime) > VSYNC_DURATION) {
+ framesOutOfSync += 1;
+ }
+ if (Math.abs(driftTime) > 2 * VSYNC_DURATION) {
+ framesSeverelyOutOfSync += 1;
+ }
+ }
+ let percentBadlyOutOfSync = 100 * framesSeverelyOutOfSync /
+ driftTimes.length;
+ let percentOutOfSync = 100 * framesOutOfSync / driftTimes.length;
+
+ let framesOutOfSyncOnlyOnce = framesOutOfSync - framesSeverelyOutOfSync;
+
+ // Calculate smoothness metric. From the formula, we can see that smoothness
+ // score can be negative.
+ let smoothnessScore = 100 - 100 * (framesOutOfSyncOnlyOnce +
+ SEVERITY * framesSeverelyOutOfSync) / driftTimes.length;
+
+ // Minimum smoothness_score value allowed is zero.
+ if (smoothnessScore < 0) {
+ smoothnessScore = 0;
+ }
+
+ return {
+ percentBadlyOutOfSync: percentBadlyOutOfSync,
+ percentOutOfSync: percentOutOfSync,
+ smoothnessScore: smoothnessScore,
+ };
+ }
+
+ return {
+ webRtcRenderingTimelineMetric,
+ };
+});
+</script>

Powered by Google App Engine
This is Rietveld 408576698