OLD | NEW |
1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
2 <!-- | 2 <!-- |
3 Copyright 2017 The Chromium Authors. All rights reserved. | 3 Copyright 2017 The Chromium Authors. All rights reserved. |
4 Use of this source code is governed by a BSD-style license that can be | 4 Use of this source code is governed by a BSD-style license that can be |
5 found in the LICENSE file. | 5 found in the LICENSE file. |
6 --> | 6 --> |
7 | 7 |
8 <link rel="import" href="/tracing/core/test_utils.html"> | 8 <link rel="import" href="/tracing/core/test_utils.html"> |
9 <link rel="import" href="/tracing/extras/importer/trace_event_importer.html"> | 9 <link rel="import" href="/tracing/extras/importer/trace_event_importer.html"> |
10 <link rel="import" href="/tracing/metrics/media_metric.html"> | 10 <link rel="import" href="/tracing/metrics/media_metric.html"> |
11 <link rel="import" href="/tracing/value/histogram_set.html"> | 11 <link rel="import" href="/tracing/value/histogram_set.html"> |
12 | 12 |
13 <script> | 13 <script> |
14 'use strict'; | 14 'use strict'; |
15 | 15 |
16 tr.b.unittest.testSuite(function() { | 16 tr.b.unittest.testSuite(function() { |
| 17 // Arbitrarily selected process ID and thread IDs we'll use in test data |
| 18 const procId = 52; |
| 19 const tidMain = 1; |
| 20 const tidCompositor = 53; |
| 21 const tidAudio = 55; |
| 22 |
| 23 function doLoadEvent(timestamp) { |
| 24 return {name: 'WebMediaPlayerImpl::DoLoad', args: {}, |
| 25 pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'}; |
| 26 } |
| 27 |
| 28 function videoRenderEvent(timestamp) { |
| 29 return {name: 'VideoRendererImpl::Render', args: {}, |
| 30 pid: procId, ts: timestamp, cat: 'media', tid: tidCompositor, ph: 'X'}; |
| 31 } |
| 32 |
| 33 function audioRenderEvent(timestamp) { |
| 34 return {name: 'AudioRendererImpl::Render', args: {}, |
| 35 pid: procId, ts: timestamp, cat: 'media', tid: tidAudio, ph: 'X'}; |
| 36 } |
| 37 |
| 38 function videoFramesDroppedEvent(timestamp, frameCount) { |
| 39 return {name: 'VideoFramesDropped', args: {count: frameCount}, |
| 40 pid: procId, ts: timestamp, cat: 'media', tid: tidCompositor, ph: 'X'}; |
| 41 } |
| 42 |
| 43 function onEndedEvent(timestamp, mediaDuration) { |
| 44 return {name: 'WebMediaPlayerImpl::OnEnded', |
| 45 args: {'duration': mediaDuration}, |
| 46 pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'}; |
| 47 } |
| 48 |
| 49 function doSeekEvent(timestamp, targetTime) { |
| 50 return {name: 'WebMediaPlayerImpl::DoSeek', args: {target: targetTime}, |
| 51 pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'}; |
| 52 } |
| 53 |
| 54 function seekedEvent(timestamp, targetTime) { |
| 55 return {name: 'WebMediaPlayerImpl::OnPipelineSeeked', |
| 56 args: {target: targetTime}, |
| 57 pid: procId, ts: timestamp, cat: 'media', tid: tidMain, ph: 'X'}; |
| 58 } |
| 59 |
| 60 function threadMarker(threadName, threadId) { |
| 61 return {name: 'thread_name', args: {name: threadName}, |
| 62 pid: procId, ts: 0, cat: '__metadata', tid: threadId, ph: 'M'}; |
| 63 } |
| 64 |
| 65 const mainThreadMarker = threadMarker('CrRendererMain', tidMain); |
| 66 const compositorThreadMarker = threadMarker('Compositor', tidCompositor); |
| 67 const audioThreadMarker = threadMarker('AudioOutputDevice', tidAudio); |
| 68 |
17 function makeModel(events) { | 69 function makeModel(events) { |
18 return tr.c.TestUtils.newModelWithEvents([events]); | 70 return tr.c.TestUtils.newModelWithEvents([events]); |
19 } | 71 } |
20 | 72 |
| 73 function checkCloseTo(histograms, histogramName, expectedValue) { |
| 74 assert.isDefined(histograms.getHistogramNamed(histogramName)); |
| 75 const value = histograms.getHistogramNamed(histogramName); |
| 76 const statistics = value.running; |
| 77 assert.strictEqual(statistics.count, 1); |
| 78 assert.closeTo(statistics.mean, expectedValue, 1e-5); |
| 79 } |
| 80 |
| 81 function checkEqual(histograms, histogramName, expectedValue) { |
| 82 assert.isDefined(histograms.getHistogramNamed(histogramName)); |
| 83 const value = histograms.getHistogramNamed(histogramName); |
| 84 const statistics = value.running; |
| 85 assert.strictEqual(statistics.count, 1); |
| 86 assert.strictEqual(statistics.mean, expectedValue); |
| 87 } |
| 88 |
21 test('mediaMetric_noData', function() { | 89 test('mediaMetric_noData', function() { |
22 const histograms = new tr.v.HistogramSet(); | 90 const histograms = new tr.v.HistogramSet(); |
23 const events = [ | 91 const events = []; |
24 {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'}, | |
25 {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'B'} | |
26 ]; | |
27 tr.metrics.mediaMetric(histograms, makeModel(events)); | 92 tr.metrics.mediaMetric(histograms, makeModel(events)); |
28 assert.lengthOf(histograms, 0); | 93 assert.lengthOf(histograms, 0); |
29 }); | 94 }); |
30 | 95 |
31 test('mediaMetric_videoTimeToPlay', function() { | 96 test('mediaMetric_videoTimeToPlay', function() { |
32 const histograms = new tr.v.HistogramSet(); | 97 const histograms = new tr.v.HistogramSet(); |
33 const events = [ | 98 const events = [ |
34 {name: 'WebMediaPlayerImpl::DoLoad', args: {}, | 99 doLoadEvent(100), |
35 pid: 52, ts: 524, cat: 'media', tid: 1, ph: 'X'}, | 100 videoRenderEvent(300), |
36 {name: 'VideoRendererImpl::Render', args: {}, | 101 // Video renderer always generate multiple render events, |
37 pid: 52, ts: 560, cat: 'media', tid: 53, ph: 'X'}, | 102 // one for each frame. For calculation of time-to-play, |
38 {name: 'VideoRendererImpl::Render', args: {}, | 103 // only the first render event is relevant. Here we put in |
39 pid: 52, ts: 580, cat: 'media', tid: 53, ph: 'X'}, | 104 // a second render event to make sure it's ignored by the |
40 {name: 'thread_name', args: {name: 'CrRendererMain'}, | 105 // metric computation code. |
41 pid: 52, ts: 0, cat: '__metadata', tid: 1, ph: 'M'}, | 106 videoRenderEvent(400), |
42 {name: 'thread_name', args: {name: 'Compositor'}, | 107 mainThreadMarker, |
43 pid: 52, ts: 0, cat: '__metadata', tid: 53, ph: 'M'}, | 108 compositorThreadMarker, |
44 ]; | 109 ]; |
45 tr.metrics.mediaMetric(histograms, makeModel(events)); | 110 tr.metrics.mediaMetric(histograms, makeModel(events)); |
46 | 111 checkCloseTo(histograms, 'time_to_video_play', 0.2); |
47 assert.isDefined(histograms.getHistogramNamed('time_to_video_play')); | |
48 const ttpValue = histograms.getHistogramNamed('time_to_video_play'); | |
49 const ttpStatistics = ttpValue.running; | |
50 assert.strictEqual(ttpStatistics.count, 1); | |
51 assert.closeTo(ttpStatistics.mean, 0.036, 1e-5); | |
52 assert.closeTo(ttpStatistics.max, 0.036, 1e-5); | |
53 }); | 112 }); |
54 | 113 |
55 test('mediaMetric_audioTimeToPlay', function() { | 114 test('mediaMetric_audioTimeToPlay', function() { |
56 const histograms = new tr.v.HistogramSet(); | 115 const histograms = new tr.v.HistogramSet(); |
57 const events = [ | 116 const events = [ |
58 {name: 'thread_name', args: {name: 'CrRendererMain'}, | 117 mainThreadMarker, |
59 pid: 52, ts: 0, cat: '__metadata', tid: 1, ph: 'M'}, | 118 audioThreadMarker, |
60 {name: 'thread_name', args: {name: 'AudioOutputDevice'}, | 119 doLoadEvent(1000), |
61 pid: 52, ts: 0, cat: '__metadata', tid: 53, ph: 'M'}, | 120 audioRenderEvent(1100), |
62 {name: 'WebMediaPlayerImpl::DoLoad', args: {}, | |
63 pid: 52, ts: 1234, cat: 'media', tid: 1, ph: 'X'}, | |
64 {name: 'AudioRendererImpl::Render', args: {}, | |
65 pid: 52, ts: 4321, cat: 'media', tid: 53, ph: 'X'}, | |
66 ]; | 121 ]; |
67 tr.metrics.mediaMetric(histograms, makeModel(events)); | 122 tr.metrics.mediaMetric(histograms, makeModel(events)); |
| 123 checkCloseTo(histograms, 'time_to_audio_play', 0.1); |
| 124 }); |
68 | 125 |
69 assert.isDefined(histograms.getHistogramNamed('time_to_audio_play')); | 126 test('mediaMetric_bufferingTimeVideo', function() { |
70 const ttpValue = histograms.getHistogramNamed('time_to_audio_play'); | 127 const histograms = new tr.v.HistogramSet(); |
71 const ttpStatistics = ttpValue.running; | 128 const events = [ |
72 assert.strictEqual(ttpStatistics.count, 1); | 129 doLoadEvent(1000), |
73 assert.closeTo(ttpStatistics.mean, 3.087, 1e-5); | 130 videoRenderEvent(1500), |
74 assert.closeTo(ttpStatistics.max, 3.087, 1e-5); | 131 videoRenderEvent(1600), |
| 132 onEndedEvent(10051500, 10), |
| 133 mainThreadMarker, |
| 134 compositorThreadMarker, |
| 135 ]; |
| 136 tr.metrics.mediaMetric(histograms, makeModel(events)); |
| 137 checkCloseTo(histograms, 'buffering_time', 50); |
| 138 }); |
| 139 |
| 140 test('mediaMetric_bufferingTimeAudio', function() { |
| 141 const histograms = new tr.v.HistogramSet(); |
| 142 const events = [ |
| 143 mainThreadMarker, |
| 144 audioThreadMarker, |
| 145 doLoadEvent(1000), |
| 146 audioRenderEvent(1500), |
| 147 onEndedEvent(5002500, 5), |
| 148 ]; |
| 149 tr.metrics.mediaMetric(histograms, makeModel(events)); |
| 150 checkCloseTo(histograms, 'buffering_time', 1); |
| 151 }); |
| 152 |
| 153 // With seek, no buffering time should be reported |
| 154 test('mediaMetric_noBufferingTime', function() { |
| 155 const histograms = new tr.v.HistogramSet(); |
| 156 const events = [ |
| 157 doLoadEvent(1000), |
| 158 videoRenderEvent(1500), |
| 159 videoRenderEvent(1600), |
| 160 onEndedEvent(10066666, 10), |
| 161 doSeekEvent(525, 1.2), |
| 162 seekedEvent(719, 1.2), |
| 163 mainThreadMarker, |
| 164 compositorThreadMarker, |
| 165 ]; |
| 166 tr.metrics.mediaMetric(histograms, makeModel(events)); |
| 167 assert.isUndefined(histograms.getHistogramNamed('buffering_time')); |
| 168 }); |
| 169 |
| 170 test('mediaMetric_droppedFrameCount', function() { |
| 171 const histograms = new tr.v.HistogramSet(); |
| 172 const events = [ |
| 173 doLoadEvent(1000), |
| 174 videoRenderEvent(1500), |
| 175 videoFramesDroppedEvent(123456, 3), |
| 176 videoFramesDroppedEvent(234567, 6), |
| 177 videoFramesDroppedEvent(345678, 1), |
| 178 mainThreadMarker, |
| 179 compositorThreadMarker, |
| 180 ]; |
| 181 tr.metrics.mediaMetric(histograms, makeModel(events)); |
| 182 checkEqual(histograms, 'dropped_frame_count', 10); |
| 183 }); |
| 184 |
| 185 test('mediaMetric_seekTime', function() { |
| 186 const histograms = new tr.v.HistogramSet(); |
| 187 const events = [ |
| 188 doLoadEvent(1000), |
| 189 videoRenderEvent(1500), |
| 190 doSeekEvent(2000, 1.2), |
| 191 seekedEvent(2500, 1.2), |
| 192 doSeekEvent(15000, 3.7), |
| 193 seekedEvent(75000, 3.7), |
| 194 mainThreadMarker, |
| 195 compositorThreadMarker, |
| 196 ]; |
| 197 tr.metrics.mediaMetric(histograms, makeModel(events)); |
| 198 checkCloseTo(histograms, 'seek_time_1.2', 0.5); |
| 199 checkCloseTo(histograms, 'seek_time_3.7', 60); |
| 200 }); |
| 201 |
| 202 // Scenario: Play mixed audio/video from start to finish |
| 203 test('mediaMetric_playVideoScenario', function() { |
| 204 const histograms = new tr.v.HistogramSet(); |
| 205 const events = [ |
| 206 doLoadEvent(2000), |
| 207 videoRenderEvent(3000), |
| 208 audioRenderEvent(3200), |
| 209 videoRenderEvent(3300), |
| 210 videoFramesDroppedEvent(123456, 4), |
| 211 videoFramesDroppedEvent(234567, 2), |
| 212 onEndedEvent(10013000, 10), |
| 213 mainThreadMarker, |
| 214 compositorThreadMarker, |
| 215 audioThreadMarker, |
| 216 ]; |
| 217 tr.metrics.mediaMetric(histograms, makeModel(events)); |
| 218 checkCloseTo(histograms, 'time_to_video_play', 1); |
| 219 checkCloseTo(histograms, 'time_to_audio_play', 1.2); |
| 220 checkCloseTo(histograms, 'buffering_time', 10); |
| 221 checkEqual(histograms, 'dropped_frame_count', 6); |
| 222 }); |
| 223 |
| 224 // Scenario: Play audio from start to finish |
| 225 test('mediaMetric_playAudioScenario', function() { |
| 226 const histograms = new tr.v.HistogramSet(); |
| 227 const events = [ |
| 228 doLoadEvent(1000), |
| 229 audioRenderEvent(1500), |
| 230 onEndedEvent(10002500, 10), |
| 231 mainThreadMarker, |
| 232 audioThreadMarker, |
| 233 ]; |
| 234 tr.metrics.mediaMetric(histograms, makeModel(events)); |
| 235 assert.isUndefined(histograms.getHistogramNamed('time_to_video_play')); |
| 236 checkCloseTo(histograms, 'time_to_audio_play', 0.5); |
| 237 checkCloseTo(histograms, 'buffering_time', 1); |
| 238 assert.isUndefined(histograms.getHistogramNamed('dropped_frame_count')); |
| 239 }); |
| 240 |
| 241 // Scenario: Play audio/video with two seeks |
| 242 test('mediaMetric_seekScenario', function() { |
| 243 const histograms = new tr.v.HistogramSet(); |
| 244 const events = [ |
| 245 doLoadEvent(1000), |
| 246 videoRenderEvent(2000), |
| 247 audioRenderEvent(2020), |
| 248 videoRenderEvent(2040), |
| 249 doSeekEvent(5000, 0.5), |
| 250 seekedEvent(5200, 0.5), |
| 251 videoFramesDroppedEvent(123456, 4), |
| 252 doSeekEvent(200000, 9), |
| 253 seekedEvent(210000, 9), |
| 254 videoFramesDroppedEvent(234567, 2), |
| 255 onEndedEvent(300000, 10), |
| 256 mainThreadMarker, |
| 257 compositorThreadMarker, |
| 258 audioThreadMarker, |
| 259 ]; |
| 260 tr.metrics.mediaMetric(histograms, makeModel(events)); |
| 261 checkCloseTo(histograms, 'time_to_video_play', 1); |
| 262 checkCloseTo(histograms, 'time_to_audio_play', 1.02); |
| 263 assert.isUndefined(histograms.getHistogramNamed('buffering_time')); |
| 264 checkEqual(histograms, 'dropped_frame_count', 6); |
| 265 checkCloseTo(histograms, 'seek_time_0.5', 0.2); |
| 266 checkCloseTo(histograms, 'seek_time_9', 10); |
75 }); | 267 }); |
76 }); | 268 }); |
77 </script> | 269 </script> |
OLD | NEW |