Index: tracing/tracing/metrics/media_metric_test.html |
diff --git a/tracing/tracing/metrics/media_metric_test.html b/tracing/tracing/metrics/media_metric_test.html |
index 64ea02b4b2ea74c171096660d8a55d7b914f8704..dd65671ceddf52a33fd1069713e67816b10791a3 100644 |
--- a/tracing/tracing/metrics/media_metric_test.html |
+++ b/tracing/tracing/metrics/media_metric_test.html |
@@ -14,16 +14,81 @@ found in the LICENSE file. |
'use strict'; |
tr.b.unittest.testSuite(function() { |
+ // Arbitrarily selected process ID and thread IDs we'll use in test data |
+ const procId = 52; |
+ const tidMain = 1; |
+ const tidCompositor = 53; |
+ const tidAudio = 55; |
+ |
+ function doLoadEvent(timestamp) { |
+ return {name: 'WebMediaPlayerImpl::DoLoad', args: {}, |
+ pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'}; |
+ } |
+ |
+ function videoRenderEvent(timestamp) { |
+ return {name: 'VideoRendererImpl::Render', args: {}, |
+ pid: procId, ts: timestamp, cat: 'media', tid: tidCompositor, ph: 'X'}; |
+ } |
+ |
+ function audioRenderEvent(timestamp) { |
+ return {name: 'AudioRendererImpl::Render', args: {}, |
+ pid: procId, ts: timestamp, cat: 'media', tid: tidAudio, ph: 'X'}; |
+ } |
+ |
+ function videoFramesDroppedEvent(timestamp, frameCount) { |
+ return {name: 'VideoFramesDropped', args: {count: frameCount}, |
+ pid: procId, ts: timestamp, cat: 'media', tid: tidCompositor, ph: 'X'}; |
+ } |
+ |
+ function onEndedEvent(timestamp, mediaDuration) { |
+ return {name: 'WebMediaPlayerImpl::OnEnded', |
+ args: {'duration': mediaDuration}, |
+ pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'}; |
+ } |
+ |
+ function doSeekEvent(timestamp, targetTime) { |
+ return {name: 'WebMediaPlayerImpl::DoSeek', args: {target: targetTime}, |
+ pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'}; |
+ } |
+ |
+ function seekedEvent(timestamp, targetTime) { |
+ return {name: 'WebMediaPlayerImpl::OnPipelineSeeked', |
+ args: {target: targetTime}, |
+ pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'}; |
+ } |
+ |
+ function threadMarker(threadName, threadId) { |
+ return {name: 'thread_name', args: {name: threadName}, |
+ pid: procId, ts: 0, cat: '__metadata', tid: threadId, ph: 'M'}; |
+ } |
+ |
+ const mainThreadMarker = threadMarker('CrRendererMain', tidMain); |
+ const compositorThreadMarker = threadMarker('Compositor', tidCompositor); |
+ const audioThreadMarker = threadMarker('AudioOutputDevice', tidAudio); |
+ |
function makeModel(events) { |
return tr.c.TestUtils.newModelWithEvents([events]); |
} |
+ function checkCloseTo(histograms, histogramName, expectedValue) { |
+ assert.isDefined(histograms.getHistogramNamed(histogramName)); |
+ const value = histograms.getHistogramNamed(histogramName); |
+ const statistics = value.running; |
+ assert.strictEqual(statistics.count, 1); |
+ assert.closeTo(statistics.mean, expectedValue, 1e-5); |
+ } |
+ |
+ function checkEqual(histograms, histogramName, expectedValue) { |
+ assert.isDefined(histograms.getHistogramNamed(histogramName)); |
+ const value = histograms.getHistogramNamed(histogramName); |
+ const statistics = value.running; |
+ assert.strictEqual(statistics.count, 1); |
+ assert.strictEqual(statistics.mean, expectedValue); |
+ } |
+ |
test('mediaMetric_noData', function() { |
const histograms = new tr.v.HistogramSet(); |
- const events = [ |
- {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'}, |
- {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'B'} |
- ]; |
+ const events = []; |
tr.metrics.mediaMetric(histograms, makeModel(events)); |
assert.lengthOf(histograms, 0); |
}); |
@@ -31,47 +96,174 @@ tr.b.unittest.testSuite(function() { |
test('mediaMetric_videoTimeToPlay', function() { |
const histograms = new tr.v.HistogramSet(); |
const events = [ |
- {name: 'WebMediaPlayerImpl::DoLoad', args: {}, |
- pid: 52, ts: 524, cat: 'media', tid: 1, ph: 'X'}, |
- {name: 'VideoRendererImpl::Render', args: {}, |
- pid: 52, ts: 560, cat: 'media', tid: 53, ph: 'X'}, |
- {name: 'VideoRendererImpl::Render', args: {}, |
- pid: 52, ts: 580, cat: 'media', tid: 53, ph: 'X'}, |
- {name: 'thread_name', args: {name: 'CrRendererMain'}, |
- pid: 52, ts: 0, cat: '__metadata', tid: 1, ph: 'M'}, |
- {name: 'thread_name', args: {name: 'Compositor'}, |
- pid: 52, ts: 0, cat: '__metadata', tid: 53, ph: 'M'}, |
+ doLoadEvent(100), |
+ videoRenderEvent(300), |
+ // Video renderer always generate multiple render events, |
+ // one for each frame. For calculation of time-to-play, |
+ // only the first render event is relevant. Here we put in |
+ // a second render event to make sure it's ignored by the |
+ // metric computation code. |
+ videoRenderEvent(400), |
+ mainThreadMarker, |
+ compositorThreadMarker, |
]; |
tr.metrics.mediaMetric(histograms, makeModel(events)); |
- |
- assert.isDefined(histograms.getHistogramNamed('time_to_video_play')); |
- const ttpValue = histograms.getHistogramNamed('time_to_video_play'); |
- const ttpStatistics = ttpValue.running; |
- assert.strictEqual(ttpStatistics.count, 1); |
- assert.closeTo(ttpStatistics.mean, 0.036, 1e-5); |
- assert.closeTo(ttpStatistics.max, 0.036, 1e-5); |
+ checkCloseTo(histograms, 'time_to_video_play', 0.2); |
}); |
test('mediaMetric_audioTimeToPlay', function() { |
const histograms = new tr.v.HistogramSet(); |
const events = [ |
- {name: 'thread_name', args: {name: 'CrRendererMain'}, |
- pid: 52, ts: 0, cat: '__metadata', tid: 1, ph: 'M'}, |
- {name: 'thread_name', args: {name: 'AudioOutputDevice'}, |
- pid: 52, ts: 0, cat: '__metadata', tid: 53, ph: 'M'}, |
- {name: 'WebMediaPlayerImpl::DoLoad', args: {}, |
- pid: 52, ts: 1234, cat: 'media', tid: 1, ph: 'X'}, |
- {name: 'AudioRendererImpl::Render', args: {}, |
- pid: 52, ts: 4321, cat: 'media', tid: 53, ph: 'X'}, |
+ mainThreadMarker, |
+ audioThreadMarker, |
+ doLoadEvent(1000), |
+ audioRenderEvent(1100), |
+ ]; |
+ tr.metrics.mediaMetric(histograms, makeModel(events)); |
+ checkCloseTo(histograms, 'time_to_audio_play', 0.1); |
+ }); |
+ |
+ test('mediaMetric_bufferingTimeVideo', function() { |
+ const histograms = new tr.v.HistogramSet(); |
+ const events = [ |
+ doLoadEvent(1000), |
+ videoRenderEvent(1500), |
+ videoRenderEvent(1600), |
+ onEndedEvent(10051500, 10), |
+ mainThreadMarker, |
+ compositorThreadMarker, |
+ ]; |
+ tr.metrics.mediaMetric(histograms, makeModel(events)); |
+ checkCloseTo(histograms, 'buffering_time', 50); |
+ }); |
+ |
+ test('mediaMetric_bufferingTimeAudio', function() { |
+ const histograms = new tr.v.HistogramSet(); |
+ const events = [ |
+ mainThreadMarker, |
+ audioThreadMarker, |
+ doLoadEvent(1000), |
+ audioRenderEvent(1500), |
+ onEndedEvent(5002500, 5), |
+ ]; |
+ tr.metrics.mediaMetric(histograms, makeModel(events)); |
+ checkCloseTo(histograms, 'buffering_time', 1); |
+ }); |
+ |
+ // With seek, no buffering time should be reported |
+ test('mediaMetric_noBufferingTime', function() { |
+ const histograms = new tr.v.HistogramSet(); |
+ const events = [ |
+ doLoadEvent(1000), |
+ videoRenderEvent(1500), |
+ videoRenderEvent(1600), |
+ onEndedEvent(10066666, 10), |
+ doSeekEvent(525, 1.2), |
+ seekedEvent(719, 1.2), |
+ mainThreadMarker, |
+ compositorThreadMarker, |
+ ]; |
+ tr.metrics.mediaMetric(histograms, makeModel(events)); |
+ assert.isUndefined(histograms.getHistogramNamed('buffering_time')); |
+ }); |
+ |
+ test('mediaMetric_droppedFrameCount', function() { |
+ const histograms = new tr.v.HistogramSet(); |
+ const events = [ |
+ doLoadEvent(1000), |
+ videoRenderEvent(1500), |
+ videoFramesDroppedEvent(123456, 3), |
+ videoFramesDroppedEvent(234567, 6), |
+ videoFramesDroppedEvent(345678, 1), |
+ mainThreadMarker, |
+ compositorThreadMarker, |
+ ]; |
+ tr.metrics.mediaMetric(histograms, makeModel(events)); |
+ checkEqual(histograms, 'dropped_frame_count', 10); |
+ }); |
+ |
+ test('mediaMetric_seekTime', function() { |
+ const histograms = new tr.v.HistogramSet(); |
+ const events = [ |
+ doLoadEvent(1000), |
+ videoRenderEvent(1500), |
+ doSeekEvent(2000, 1.2), |
+ seekedEvent(2500, 1.2), |
+ doSeekEvent(15000, 3.7), |
+ seekedEvent(75000, 3.7), |
+ mainThreadMarker, |
+ compositorThreadMarker, |
+ ]; |
+ tr.metrics.mediaMetric(histograms, makeModel(events)); |
+ checkCloseTo(histograms, 'seek_time_1.2', 0.5); |
+ checkCloseTo(histograms, 'seek_time_3.7', 60); |
+ }); |
+ |
+ // Scenario: Play mixed audio/video from start to finish |
+ test('mediaMetric_playVideoScenario', function() { |
+ const histograms = new tr.v.HistogramSet(); |
+ const events = [ |
+ doLoadEvent(2000), |
+ videoRenderEvent(3000), |
+ audioRenderEvent(3200), |
+ videoRenderEvent(3300), |
+ videoFramesDroppedEvent(123456, 4), |
+ videoFramesDroppedEvent(234567, 2), |
+ onEndedEvent(10013000, 10), |
+ mainThreadMarker, |
+ compositorThreadMarker, |
+ audioThreadMarker, |
+ ]; |
+ tr.metrics.mediaMetric(histograms, makeModel(events)); |
+ checkCloseTo(histograms, 'time_to_video_play', 1); |
+ checkCloseTo(histograms, 'time_to_audio_play', 1.2); |
+ checkCloseTo(histograms, 'buffering_time', 10); |
+ checkEqual(histograms, 'dropped_frame_count', 6); |
+ }); |
+ |
+ // Scenario: Play audio from start to finish |
+ test('mediaMetric_playAudioScenario', function() { |
+ const histograms = new tr.v.HistogramSet(); |
+ const events = [ |
+ doLoadEvent(1000), |
+ audioRenderEvent(1500), |
+ onEndedEvent(10002500, 10), |
+ mainThreadMarker, |
+ audioThreadMarker, |
]; |
tr.metrics.mediaMetric(histograms, makeModel(events)); |
+ assert.isUndefined(histograms.getHistogramNamed('time_to_video_play')); |
+ checkCloseTo(histograms, 'time_to_audio_play', 0.5); |
+ checkCloseTo(histograms, 'buffering_time', 1); |
+ assert.isUndefined(histograms.getHistogramNamed('dropped_frame_count')); |
+ }); |
- assert.isDefined(histograms.getHistogramNamed('time_to_audio_play')); |
- const ttpValue = histograms.getHistogramNamed('time_to_audio_play'); |
- const ttpStatistics = ttpValue.running; |
- assert.strictEqual(ttpStatistics.count, 1); |
- assert.closeTo(ttpStatistics.mean, 3.087, 1e-5); |
- assert.closeTo(ttpStatistics.max, 3.087, 1e-5); |
+ // Scenario: Play audio/video with two seeks |
+ test('mediaMetric_seekScenario', function() { |
+ const histograms = new tr.v.HistogramSet(); |
+ const events = [ |
+ doLoadEvent(1000), |
+ videoRenderEvent(2000), |
+ audioRenderEvent(2020), |
+ videoRenderEvent(2040), |
+ doSeekEvent(5000, 0.5), |
+ seekedEvent(5200, 0.5), |
+ videoFramesDroppedEvent(123456, 4), |
+ doSeekEvent(200000, 9), |
+ seekedEvent(210000, 9), |
+ videoFramesDroppedEvent(234567, 2), |
+ onEndedEvent(300000, 10), |
+ mainThreadMarker, |
+ compositorThreadMarker, |
+ audioThreadMarker, |
+ ]; |
+ tr.metrics.mediaMetric(histograms, makeModel(events)); |
+ checkCloseTo(histograms, 'time_to_video_play', 1); |
+ checkCloseTo(histograms, 'time_to_audio_play', 1.02); |
+ assert.isUndefined(histograms.getHistogramNamed('buffering_time')); |
+ checkEqual(histograms, 'dropped_frame_count', 6); |
+ checkCloseTo(histograms, 'seek_time_0.5', 0.2); |
+ checkCloseTo(histograms, 'seek_time_9', 10); |
}); |
}); |
</script> |