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

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

Issue 2711623002: Add a TBMv2 webrtc_rendering_metric. (Closed)
Patch Set: Customize summary options. 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 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..8747508f2c6add43801ccbf29b5802fbbcb95a6f
--- /dev/null
+++ b/tracing/tracing/metrics/webrtc/webrtc_rendering_metric.html
@@ -0,0 +1,339 @@
+<!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;
+ // 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;
+ // How many vsyncs a frame should be displayed to be considered frozen.
+ const FROZEN_FRAME_VSYNC_COUNT_THRESHOLD = 6;
+
+ 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 count_smallerIsBetter =
+ tr.b.Unit.byName.count_smallerIsBetter;
+ 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_MEDIA_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(histograms, model) {
+ tr.metrics.v8.utils.groupAndProcessEvents(model,
nednguyen 2017/03/09 19:21:56 Actually, why is this metric reusing a v8 metric u
+ isValidEvent,
+ event => event.args[STREAM_ID_NAME],
+ (streamName, events) => getTimeStats(histograms, streamName, events)
+ );
+ }
+
+ tr.metrics.MetricRegistry.register(webrtcRenderingMetric);
+
+ function addHistogram(samples, histograms, name, unit, opt_summaryOptions) {
+ let summaryOptions = opt_summaryOptions;
+ if (!summaryOptions) {
+ // By default, we store a single value, so we only need one of the
+ // statistics to keep track. We choose the average for that.
+ summaryOptions = {
+ count: false,
+ max: false,
+ min: false,
+ std: false,
+ sum: false,
+ };
+ }
+ let histogram = new tr.v.Histogram(name, unit);
+ for (let sample of samples) {
+ histogram.addSample(sample);
+ }
+ histogram.customizeSummaryOptions(summaryOptions);
+ histograms.addHistogram(histogram);
+ }
+
+ function getTimeStats(histograms, streamName, events) {
+ let frameHist = getFrameDistribution(histograms, events);
+ addFpsFromFrameDistribution(histograms, frameHist);
+ addFreezingScore(histograms, frameHist);
+ let driftTimeStats = getDriftStats(events);
+ addHistogram(driftTimeStats.driftTime, histograms,
+ 'WebRTCRendering_drift_time', timeDurationInMs_smallerIsBetter,
+ {count: false, min: false, percentile: [0.75, 0.9]});
+ addHistogram([driftTimeStats.renderingLengthError], histograms,
+ 'WebRTCRendering_rendering_length_error', percentage_smallerIsBetter);
+ let smoothnessStats = getSmoothnessStats(driftTimeStats.driftTime);
+ addHistogram([smoothnessStats.percentBadlyOutOfSync], histograms,
+ 'WebRTCRendering_percent_badly_out_of_sync',
+ percentage_smallerIsBetter);
+ addHistogram([smoothnessStats.percentOutOfSync], histograms,
+ 'WebRTCRendering_percent_out_of_sync', percentage_smallerIsBetter);
+ addHistogram([smoothnessStats.smoothnessScore], histograms,
+ 'WebRTCRendering_smoothness_score', percentage_biggerIsBetter);
+ addHistogram([smoothnessStats.framesOutOfSync], histograms,
+ 'WebRTCRendering_frames_out_of_sync', count_smallerIsBetter);
+ addHistogram([smoothnessStats.framesSeverelyOutOfSync], histograms,
+ 'WebRTCRendering_frames_badly_out_of_sync', count_smallerIsBetter);
+ }
+
+ /**
+ * Create the frame 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 {tr.v.HistogramSet} histograms
+ * @param {Array.<event>} events - An array of events.
+ * @returns {tr.v.Histogram} frameHist - The frame distribution.
+ */
+ function getFrameDistribution(histograms, events) {
+ const cadence = tr.b.runLengthEncoding(
+ events.map(e => e.args[IDEAL_RENDER_INSTANT_NAME]));
+ const frameHist = new tr.v.Histogram('WebRTCRendering_frame_distribution',
+ count_smallerIsBetter,
+ tr.v.HistogramBinBoundaries.createLinear(1, 50, 49));
+ for (const ticks of cadence) {
+ frameHist.addSample(ticks.count);
+ }
+ frameHist.customizeSummaryOptions({percentile: [0.75, 0.9]});
+ histograms.addHistogram(frameHist);
+ return frameHist;
+ }
+
+ /**
+ * 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 {tr.v.HistogramSet} histograms
+ * @param {tr.v.Histogram} frameHist - The frame distribution. See
+ * getFrameDistribution.
+ */
+ function addFpsFromFrameDistribution(histograms, frameHist) {
+ let numberFrames = 0;
+ let numberVsyncs = 0;
+ for (let ticks = 1; ticks < frameHist.allBins.length; ++ticks) {
+ const count = frameHist.allBins[ticks].count;
+ numberFrames += count;
+ numberVsyncs += ticks * count;
+ }
+ let meanRatio = numberVsyncs / numberFrames;
+ addHistogram([DISPLAY_HERTZ / meanRatio], histograms, 'WebRTCRendering_fps',
+ unitlessNumber_biggerIsBetter);
+ }
+
+ /**
+ * Returns the weighted penalty for a number of frozen frames.
+ *
+ * In a series of repeated frames of length > 5, all frames after the first
+ * are considered frozen. Conversely, no frames in a series of repeated frames
+ * of length <= 5 will be considered frozen.
+ *
+ * This means the weight for 0 to 4 frozen frames is 0.
+ *
+ * @param {Number} numberFrozenFrames - The number of frozen frames.
+ * @returns {Number} - The weight penalty for the number of frozen frames.
+ */
+ function frozenPenaltyWeight(numberFrozenFrames) {
+ const penalty = {
+ 5: 1,
+ 6: 5,
+ 7: 15,
+ 8: 25
+ };
+ return penalty[numberFrozenFrames] || (8 * (numberFrozenFrames - 4));
+ }
+
+ /**
+ * Adds the freezing score.
+ *
+ * @param {tr.v.HistogramSet} histograms
+ * @param {tr.v.Histogram} frameHist - The frame distribution.
+ * See getFrameDistribution.
+ */
+ function addFreezingScore(histograms, frameHist) {
+ let numberVsyncs = 0;
+ let freezingScore = 0;
+ let frozenFramesCount = 0;
+ for (let ticks = 1; ticks < frameHist.allBins.length; ++ticks) {
+ const count = frameHist.allBins[ticks].count;
+ numberVsyncs += ticks * count;
+ if (ticks >= FROZEN_FRAME_VSYNC_COUNT_THRESHOLD) {
+ // The first frame of the series is not considered frozen.
+ frozenFramesCount += count * (ticks - 1);
+ freezingScore += count * frozenPenaltyWeight(ticks - 1);
+ }
+ }
+ freezingScore = 1 - freezingScore / numberVsyncs;
+ if (freezingScore < 0) {
+ freezingScore = 0;
+ }
+ addHistogram([frozenFramesCount], histograms,
+ 'WebRTCRendering_frozen_frames_count', count_smallerIsBetter);
+ addHistogram([freezingScore], histograms, 'WebRTCRendering_freezing_score',
+ percentage_biggerIsBetter);
+ }
+
+ /**
+ * Get the drift time statistics.
+ *
+ * This method will calculate:
+ * - Drift Time: The difference between the Actual Render Begin and the Ideal
+ * Render Instant for each event.
+ * - Rendering Length Error: The alignment error of the Ideal Render
+ * Instants. The Ideal Render Instants should be equally spaced by
+ * intervals of length VSYNC_DURATION_US. The Rendering Length error
+ * measures how much they are misaligned.
+ *
+ * @param {Array.<event>} events - An array of events.
+ * @returns {Object.<Array.<Number>, Number>} - The drift time and rendering
+ * length error.
+ */
+ 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 = discrepancySum / idealRenderSpan;
+
+ return {driftTime, 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.<Number, Number, Number>} - The percentBadlyOutOfSync,
+ * percentOutOfSync and smoothnesScore calculated from the driftTimes array.
+ */
+ function getSmoothnessStats(driftTimes) {
+ let meanDriftTime = tr.b.Statistics.mean(driftTimes);
+ let normDriftTimes = driftTimes.map(driftTime =>
+ Math.abs(driftTime - meanDriftTime));
+
+ // How many times is a frame later/earlier than T=2*VSYNC_DURATION_US. Time
+ // is in microseconds
+ let framesSeverelyOutOfSync = normDriftTimes
+ .filter(driftTime => driftTime > 2 * VSYNC_DURATION_US)
+ .length;
+ // How many times is a frame later/earlier than VSYNC_DURATION_US.
+ let framesOutOfSync = normDriftTimes
+ .filter(driftTime => driftTime > VSYNC_DURATION_US)
+ .length;
+
+ let percentBadlyOutOfSync = framesSeverelyOutOfSync /
+ driftTimes.length;
+ let percentOutOfSync = framesOutOfSync / driftTimes.length;
+
+ let framesOutOfSyncOnlyOnce = framesOutOfSync - framesSeverelyOutOfSync;
+
+ // Calculate smoothness metric. From the formula, we can see that smoothness
+ // score can be negative.
+ let smoothnessScore = 1 - (framesOutOfSyncOnlyOnce +
+ SEVERITY * framesSeverelyOutOfSync) / driftTimes.length;
+
+ // Minimum smoothness_score value allowed is zero.
+ if (smoothnessScore < 0) {
+ smoothnessScore = 0;
+ }
+
+ return {
+ framesOutOfSync,
+ framesSeverelyOutOfSync,
+ percentBadlyOutOfSync,
+ percentOutOfSync,
+ smoothnessScore
+ };
+ }
+
+ return {
+ webrtcRenderingMetric,
+ };
+});
+</script>
« no previous file with comments | « tracing/tracing/metrics/all_metrics.html ('k') | tracing/tracing/metrics/webrtc/webrtc_rendering_metric_test.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698