1# Copyright 2015 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4import unittest 5 6from telemetry.web_perf.metrics import webrtc_rendering_stats as stats_helper 7 8 9class FakeEvent(object): 10 """Fake event class to mock rendering events.""" 11 12 def __init__(self, **kwargs): 13 """Initializer for the fake WebMediaPlayerMS::UpdateCurrentFrame events. 14 15 The WebMediaPlayerMsRenderingStats only cares about actual render begin, 16 actual render end, ideal render instant and serial fields of the events. 17 So we only define these four fields here in this fake event class. 18 This method is written so as to take whatever valid parameters from the 19 event definition. It can also be used to craft incomplete events. 20 21 Args: 22 kwargs::= dict('actual_begin', 'actual_end', 'ideal_instant', 'serial'). 23 """ 24 self.args = {} 25 name_map = { 26 'Actual Render Begin': 'actual_begin', 27 'Actual Render End': 'actual_end', 28 'Ideal Render Instant': 'ideal_instant', 29 'Serial': 'serial'} 30 for internal_name, external_name in name_map.iteritems(): 31 if external_name in kwargs: 32 self.args[internal_name] = kwargs[external_name] 33 34 35class WebMediaPlayerMsRenderingStatsTest(unittest.TestCase): 36 37 def setUp(self): 38 # A local stream id always has an even number. 39 # A remote stream id always has an odd number. 40 self.local_stream = 136390988 41 self.remote_stream = 118626165 42 43 def testInitialization(self): 44 event_local_stream = FakeEvent(actual_begin=1655987203306, 45 actual_end=1655987219972, ideal_instant=1655987154324, 46 serial=self.local_stream) 47 48 event_remote_stream = FakeEvent(actual_begin=1655987203306, 49 actual_end=1655987219972, ideal_instant=1655987167999, 50 serial=self.remote_stream) 51 52 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats( 53 [event_local_stream, event_remote_stream]) 54 55 self.assertEqual(2, len(stats_parser.stream_to_events)) 56 57 self.assertEqual(event_local_stream.args, 58 stats_parser.stream_to_events[self.local_stream][0].args) 59 60 self.assertEqual(event_remote_stream.args, 61 stats_parser.stream_to_events[self.remote_stream][0].args) 62 63 def testInvalidEvents(self): 64 event_missing_serial = FakeEvent(actual_begin=1655987244074, 65 actual_end=1655987260740, ideal_instant=1655987204839) 66 67 event_missing_actual_begin = FakeEvent(actual_end=1655987260740, 68 ideal_instant=1655987217999, serial=self.local_stream) 69 70 event_missing_actual_end = FakeEvent(actual_end=1655987260740, 71 ideal_instant=1655987217999, serial=self.remote_stream) 72 73 event_missing_ideal_instant = FakeEvent(actual_begin=1655987260740, 74 actual_end=1655987277406, serial=self.remote_stream) 75 76 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats( 77 [event_missing_serial, event_missing_actual_begin, 78 event_missing_actual_end, event_missing_ideal_instant]) 79 80 self.assertEqual(0, len(stats_parser.stream_to_events)) 81 82 def _GetFakeEvents(self): 83 fake_events = [ 84 FakeEvent(actual_begin=1663780195583, actual_end=1663780212249, 85 ideal_instant=1663780179998, serial=self.remote_stream), 86 FakeEvent(actual_begin=1663780212249, actual_end=1663780228915, 87 ideal_instant=1663780179998, serial=self.remote_stream), 88 FakeEvent(actual_begin=1663780228915, actual_end=1663780245581, 89 ideal_instant=1663780197998, serial=self.remote_stream), 90 FakeEvent(actual_begin=1663780245581, actual_end=1663780262247, 91 ideal_instant=1663780215998, serial=self.remote_stream), 92 FakeEvent(actual_begin=1663780262247, actual_end=1663780278913, 93 ideal_instant=1663780215998, serial=self.remote_stream), 94 FakeEvent(actual_begin=1663780278913, actual_end=1663780295579, 95 ideal_instant=1663780254998, serial=self.remote_stream), 96 FakeEvent(actual_begin=1663780295579, actual_end=1663780312245, 97 ideal_instant=1663780254998, serial=self.remote_stream), 98 FakeEvent(actual_begin=1663780312245, actual_end=1663780328911, 99 ideal_instant=1663780254998, serial=self.remote_stream), 100 FakeEvent(actual_begin=1663780328911, actual_end=1663780345577, 101 ideal_instant=1663780310998, serial=self.remote_stream), 102 FakeEvent(actual_begin=1663780345577, actual_end=1663780362243, 103 ideal_instant=1663780310998, serial=self.remote_stream), 104 FakeEvent(actual_begin=1663780362243, actual_end=1663780378909, 105 ideal_instant=1663780310998, serial=self.remote_stream), 106 FakeEvent(actual_begin=1663780378909, actual_end=1663780395575, 107 ideal_instant=1663780361998, serial=self.remote_stream), 108 FakeEvent(actual_begin=1663780395575, actual_end=1663780412241, 109 ideal_instant=1663780361998, serial=self.remote_stream), 110 FakeEvent(actual_begin=1663780412241, actual_end=1663780428907, 111 ideal_instant=1663780361998, serial=self.remote_stream), 112 FakeEvent(actual_begin=1663780428907, actual_end=1663780445573, 113 ideal_instant=1663780412998, serial=self.remote_stream)] 114 115 return fake_events 116 117 def _GetCorruptEvents(self): 118 # The events below are corrupt data because the |ideal_instant| 119 # parameter is zero, which makes all computation meaningless. 120 # Indeed, the ideal_instant (aka Ideal Render Instant) indicates 121 # when the frame should be rendered ideally. 122 corrupt_events = [ 123 FakeEvent(actual_begin=1663780195583, actual_end=1663780212249, 124 ideal_instant=0, serial=self.remote_stream), 125 FakeEvent(actual_begin=1663780212249, actual_end=1663780228915, 126 ideal_instant=0, serial=self.remote_stream), 127 FakeEvent(actual_begin=1663780228915, actual_end=1663780245581, 128 ideal_instant=0, serial=self.remote_stream), 129 FakeEvent(actual_begin=1663780245581, actual_end=1663780262247, 130 ideal_instant=0, serial=self.remote_stream)] 131 return corrupt_events 132 133 def testGetCadence(self): 134 fake_events = self._GetFakeEvents() 135 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats(fake_events) 136 # The events defined in _GetFakeEvents above show that the first source 137 # framee of ideal_instant=1663780179998 is rendered twice, then 138 # the second source frame of ideal_instant=1663780197998 is rendered once 139 # the third source frame of ideal_instant=1663780215998 is rendered twice 140 # and so on. The expected cadence will therefore be [2 1 2 etc..] 141 expected_cadence = [2, 1, 2, 3, 3, 3, 1] 142 self.assertEqual(expected_cadence, stats_parser._GetCadence(fake_events)) 143 144 def testGetSourceToOutputDistribution(self): 145 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats([]) 146 cadence = [2, 1, 2, 3, 3, 3, 1] 147 expected_frame_distribution = {1: 2, 2: 2, 3: 3} 148 self.assertEqual(expected_frame_distribution, 149 stats_parser._GetSourceToOutputDistribution(cadence)) 150 151 def testGetFpsFromCadence(self): 152 frame_distribution = {1: 2, 2: 2, 3: 3} 153 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats([]) 154 expected_frame_rate = 28.0 155 self.assertEqual(expected_frame_rate, 156 stats_parser._GetFpsFromCadence(frame_distribution)) 157 158 def testGetFrozenFramesReports(self): 159 frame_distribution = {1: 2, 2: 2, 3: 569, 6: 1} 160 expected_frozen_reports = [{'frozen_frames': 5, 'occurrences': 1}] 161 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats([]) 162 self.assertEqual(expected_frozen_reports, 163 stats_parser._GetFrozenFramesReports(frame_distribution)) 164 165 def testIsRemoteStream(self): 166 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats([]) 167 self.assertTrue(stats_parser._IsRemoteStream(self.remote_stream)) 168 169 def testGetDrifTimeStats(self): 170 fake_events = self._GetFakeEvents() 171 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats([]) 172 cadence = stats_parser._GetCadence(fake_events) 173 expected_drift_time = [15585, 30917, 29583, 23915, 17913, 16911, 15909] 174 expected_rendering_length_error = 29.613733905579398 175 176 self.assertEqual((expected_drift_time, expected_rendering_length_error), 177 stats_parser._GetDrifTimeStats(fake_events, cadence)) 178 179 def testGetSmoothnessStats(self): 180 norm_drift_time = [5948.2857142857138, 9383.7142857142862, 181 8049.7142857142862, 2381.7142857142862, 3620.2857142857138, 182 4622.2857142857138, 5624.2857142857138] 183 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats([]) 184 expected_percent_badly_oos = 0.0 185 expected_percent_out_of_sync = 0.0 186 expected_smoothness_score = 100.0 187 expected_smoothness_stats = (expected_percent_badly_oos, 188 expected_percent_out_of_sync, expected_smoothness_score) 189 190 self.assertEqual(expected_smoothness_stats, 191 stats_parser._GetSmoothnessStats(norm_drift_time)) 192 193 def testNegativeSmoothnessScoreChangedToZero(self): 194 norm_drift_time = [15948.285714285714, 9383.714285714286, 195 28049.714285714286, 72381.71428571429, 3620.2857142857138, 196 4622.285714285714, 35624.28571428572] 197 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats([]) 198 expected_percent_badly_oos = 28.571428571428573 199 expected_percent_out_of_sync = 42.857142857142854 200 expected_smoothness_score = 0.0 201 expected_smoothness_stats = (expected_percent_badly_oos, 202 expected_percent_out_of_sync, expected_smoothness_score) 203 204 self.assertEqual(expected_smoothness_stats, 205 stats_parser._GetSmoothnessStats(norm_drift_time)) 206 207 def testGetFreezingScore(self): 208 frame_distribution = {1: 2, 2: 2, 3: 569, 6: 1} 209 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats([]) 210 expected_freezing_score = 99.94182664339732 211 self.assertEqual(expected_freezing_score, 212 stats_parser._GetFreezingScore(frame_distribution)) 213 214 def testNegativeFrezingScoreChangedToZero(self): 215 frame_distribution = {1: 2, 2: 2, 3: 2, 8:100} 216 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats([]) 217 self.assertEqual(0.0, stats_parser._GetFreezingScore(frame_distribution)) 218 219 def testGetTimeStats(self): 220 fake_events = self._GetFakeEvents() 221 expected_frame_dist = {1: 2, 2: 2, 3: 3} 222 expected_frame_rate = 28.0 223 expected_drift_time = [15585, 30917, 29583, 23915, 17913, 16911, 15909] 224 expected_rendering_length_error = 29.613733905579398 225 expected_percent_badly_oos = 0.0 226 expected_percent_out_of_sync = 0.0 227 expected_smoothness_score = 100.0 228 expected_freezing_score = 100.0 229 230 stats_cls = stats_helper.WebMediaPlayerMsRenderingStats 231 232 stats_parser = stats_cls(fake_events) 233 234 expected_stats = stats_helper.TimeStats( 235 drift_time=expected_drift_time, 236 percent_badly_out_of_sync=expected_percent_badly_oos, 237 percent_out_of_sync=expected_percent_out_of_sync, 238 smoothness_score=expected_smoothness_score, 239 freezing_score=expected_freezing_score, 240 rendering_length_error=expected_rendering_length_error, 241 fps=expected_frame_rate, 242 frame_distribution=expected_frame_dist) 243 244 stats = stats_parser.GetTimeStats() 245 246 self.assertEqual(expected_stats.drift_time, stats.drift_time) 247 self.assertEqual(expected_stats.percent_badly_out_of_sync, 248 stats.percent_badly_out_of_sync) 249 self.assertEqual(expected_stats.percent_out_of_sync, 250 stats.percent_out_of_sync) 251 self.assertEqual(expected_stats.smoothness_score, stats.smoothness_score) 252 self.assertEqual(expected_stats.freezing_score, stats.freezing_score) 253 self.assertEqual(expected_stats.rendering_length_error, 254 stats.rendering_length_error) 255 self.assertEqual(expected_stats.fps, stats.fps) 256 self.assertEqual(expected_stats.frame_distribution, 257 stats.frame_distribution) 258 259 def testCorruptData(self): 260 corrupt_events = self._GetCorruptEvents() 261 stats_parser = stats_helper.WebMediaPlayerMsRenderingStats(corrupt_events) 262 stats = stats_parser.GetTimeStats() 263 self.assertTrue(stats.invalid_data) 264 self.assertIsNone(stats.drift_time) 265 self.assertIsNone(stats.percent_badly_out_of_sync) 266 self.assertIsNone(stats.percent_out_of_sync) 267 self.assertIsNone(stats.smoothness_score) 268 self.assertIsNone(stats.freezing_score) 269 self.assertIsNone(stats.rendering_length_error) 270 self.assertIsNone(stats.fps) 271 self.assertIsNone(stats.frame_distribution) 272