Chromium Code Reviews| 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> |