• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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