| 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>
|
|
|