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

Unified 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, 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_metric.html
diff --git a/tracing/tracing/metrics/webrtc/webrtc_rendering_metric.html b/tracing/tracing/metrics/webrtc/webrtc_rendering_metric.html
new file mode 100644
index 0000000000000000000000000000000000000000..45049e95496fcb2ace389ce30e3827a507a89297
--- /dev/null
+++ b/tracing/tracing/metrics/webrtc/webrtc_rendering_metric.html
@@ -0,0 +1,344 @@
+<!DOCTYPE html>
+<!--
+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/base/utils.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_US = 1e6 / DISPLAY_HERTZ;
+ // When to consider a frame frozen (in VSYNC units) meaning 1 initial
+ // frame + 5 repeats of that frame.
+ const FROZEN_FRAME_VSYNC_COUNT_THRESHOLD = 6;
+ // How much more severe is a 'Badly out of sync' render event compared to an
+ // 'Out of sync' one when calculating the smoothness score.
+ const SEVERITY = 3;
+
+ const WEB_MEDIA_PLAYER_UPDATE_TITLE = 'WebMediaPlayerMS::UpdateCurrentFrame';
+ // These four are args for WebMediaPlayerMS update events.
+ const IDEAL_RENDER_INSTANT_NAME = 'Ideal Render Instant';
+ const ACTUAL_RENDER_BEGIN_NAME = 'Actual Render Begin';
+ const ACTUAL_RENDER_END_NAME = 'Actual Render End';
+ // The events of interest have a 'Serial' argument which represents the
+ // stream ID.
+ const STREAM_ID_NAME = 'Serial';
+
+ const REQUIRED_EVENT_ARGS_NAMES = [
+ IDEAL_RENDER_INSTANT_NAME, ACTUAL_RENDER_BEGIN_NAME, ACTUAL_RENDER_END_NAME,
+ STREAM_ID_NAME
+ ];
+
+ 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;
+
+ /*
+ * 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_MEDIRA_PLAYER_UPDATE_TITLE || !event.args) {
+ return false;
+ }
+ for (let parameter of REQUIRED_EVENT_ARGS_NAMES) {
+ if (!(parameter in event.args)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function webrtcRenderingMetric(values, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
+ isValidEvent,
+ event => event.args[STREAM_ID_NAME],
+ eventStream,
+ (streamName, events) => getTimeStats(values, streamName, events)
+ );
+ }
+
+ tr.metrics.MetricRegistry.register(webrtcRenderingMetric);
+
+ function addHistogram(scalarValues, values, name, unit) {
+ 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 frameDistribution = getSourceToOutputDistribution(events);
+ addFpsFromCadence(values, frameDistribution);
+ addFreezeScore(values, frameDistribution);
+ let driftTimeStats = getDriftTimeStats(events);
+ addHistogram(driftTimeStats.driftTime, values,
+ 'WebRTCRendering_drift_time', timeDurationInMs_smallerIsBetter);
+ addHistogram([driftTimeStats.renderingLengthError], values,
+ 'WebRTCRendering_rendering_length_error', percentage_smallerIsBetter);
+ let smoothnessStats = getSmoothnessStats(driftTimeStats.driftTime);
+ addHistogram([smoothnessStats.percentBadlyOutOfSync], values,
+ 'WebRTCRendering_percent_badly_out_of_sync',
+ percentage_smallerIsBetter);
+ addHistogram([smoothnessStats.percentOutOfSync], values,
+ 'WebRTCRendering_percent_out_of_sync', percentage_smallerIsBetter);
+ addHistogram([smoothnessStats.smoothnessScore], values,
+ 'WebRTCRendering_smoothness_score', percentage_biggerIsBetter);
+ }
+
+ /**
+ * Create the source to output distribution.
+ *
+ * If the overall display distribution is A1:A2:..:An, this will tell how
+ * many times a frame stays displayed during Ak*VSYNC_DURATION_US, also known
+ * as 'source to output' distribution.
+ *
+ * In other terms, a distribution B where
+ * B[k] = number of frames that are displayed k times.
+ *
+ * @param {Array.<event>} cadence - An array of events.
+ * @returns {Map} frameDistribution - The source to output distribution.
+ */
+ function getSourceToOutputDistribution(events) {
+ let cadence = tr.b.runLengthEncoding(
+ events.map(e => e.args[IDEAL_RENDER_INSTANT]));
+ let frameDistribution = new Map();
+ for (let ticks of cadence) {
+ frameDistribution.set(ticks.count,
+ 1 + (frameDistribution.get(ticks.count) || 0));
+ }
+ 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 {Map} frameDistribution - See getSourceToOutputDistribution.
+ */
+ function addFpsFromCadence(values, frameDistribution) {
+ let numberFrames = 0;
+ let numberVsyncs = 0;
+ for (let [ticks, count] of frameDistribution) {
+ numberFrames += count;
+ numberVsyncs += ticks * count;
+ }
+ let meanRatio = numberVsyncs / numberFrames;
+ addHistogram([DISPLAY_HERTZ / meanRatio], values, 'WebRTCRendering_fps',
+ unitlessNumber_biggerIsBetter);
+ }
+
+ /**
+ * Find evidence of frozen frames in distribution.
+ *
+ * For simplicity we count as freezing the frames that are repeated at least
+ * five 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.
+ * We mitigate this by saying anything unde 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 than the frozen frame threshold (6).
+ *
+ * @param {Map} frameDistribution - See getSourceToOutputDistribution.
+ * @return {Map} frozenFrames - The frozen frames distribution.
+ */
+ function getFrozenFramesReports(frameDistribution) {
+ frozenFrames = {};
+ for (let [ticks, count] of frameDistribution) {
+ if (ticks >= FROZEN_FRAME_VSYNC_COUNT_THRESHOLD) {
+ frozenFrames.set(ticks - 1, count);
+ }
+ }
+ return frozenFrames;
+ }
+
+ /**
+ * Returns the weighted penalty for a number of frozen frames.
+ *
+ * We count for frozen anything above 6 vsync display 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 {Map} frameDistribution - See getSourceToOutputDistribution.
+ */
+ function addFreezingScore(values, frameDistribution) {
+ let numberVsyncs = 0;
+ for (let [ticks, count] of frameDistribution) {
+ numberVsyncs += ticks * count;
+ }
+ let frozenFrames = getFrozenFramesReports(frameDistribution);
+ let freezingScore = 100;
+ for (let [ticks, count] of frozenFrames) {
+ let weight = frozenPenaltyWeight(ticks);
+ freezingScore -= 100 * count / numberVsyncs * weight;
+ if (freezingScore < 0) {
+ freezingScore = 0;
+ }
+ }
+ addHistogram([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 {Array.<Number>} events - An array of events.
+ * @returns {Object} - The driftTime and renderingLengthError calculated
+ * from the cadence.
+ */
+ function getDriftStats(events) {
+ let driftTime = [];
+ let discrepancy = [];
+ let oldIdealRender = 0;
+ let expectedIdealRender = 0;
+
+ for (let event of events) {
+ let currentIdealRender = event.args[IDEAL_RENDER_INSTANT_NAME];
+ // The expected time of the next 'Ideal Render' event begins as the
+ // current 'Ideal Render' time and increases by VSYNC_DURATION_US on every
+ // frame.
+ expectedIdealRender += VSYNC_DURATION_US;
+ if (currentIdealRender === oldIdealRender) {
+ continue;
+ }
+ let actualRenderBegin = event.args[ACTUAL_RENDER_BEGIN_NAME];
+ // When was the frame rendered vs. when it would've been ideal.
+ driftTime.push(actualRenderBegin - currentIdealRender);
+ // The discrepancy is the absolute difference between the current Ideal
+ // Render and the expected Ideal Render.
+ discrepancy.push(Math.abs(currentIdealRender - expectedIdealRender));
+ expectedIdealRender = currentIdealRender;
+ oldIdealRender = currentIdealRender;
+ }
+
+ let discrepancySum = tr.b.Statistics.sum(discrepancy) - discrepancy[0];
+ let lastIdealRender =
+ events[events.length - 1].args[IDEAL_RENDER_INSTANT_NAME];
+ let firstIdealRender = events[0].args[IDEAL_RENDER_INSTANT_NAME];
+ let idealRenderSpan = lastIdealRender - firstIdealRender;
+
+ let renderingLengthError = 100 * discrepancySum / idealRenderSpan;
+
+ 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_US.
+ * To be considered out of sync, a frame has to have missed rendering by at
+ * least one VSYNC_DURATION_US.
+ * The smoothness score is a measure of how out of sync the frames are.
+ *
+ * @param {Array.<Number>} 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_US. Time
+ // is in microseconds
+ let framesSeverelyOutOfSync = 0;
+ // How many times is a frame later/earlier than VSYNC_DURATION_US.
+ let framesOutOfSync = 0;
+ for (let driftTime of normDriftTimes) {
+ if (Math.abs(driftTime) > VSYNC_DURATION_US) {
+ framesOutOfSync += 1;
+ }
+ if (Math.abs(driftTime) > 2 * VSYNC_DURATION_US) {
+ 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 {
+ webrtcRenderingMetric,
+ };
+});
+</script>

Powered by Google App Engine
This is Rietveld 408576698