Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(409)

Side by Side Diff: tools/perf/measurements/webrtc_rendering.py

Issue 1254023003: Telemetry Test for WebRTC Rendering. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Added rendering_lenth_error and normalized drift_time and smoothness_score Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4 import numpy
5 import logging
6
7 from telemetry.timeline import tracing_category_filter
8 from telemetry.timeline import tracing_options
9 from telemetry.timeline import model
10 from telemetry.page import page_test
11 from telemetry.value import scalar
12 from telemetry.value import list_of_scalar_values
13 from telemetry.value import list_of_string_values
14 from metrics import power
15
16
17 DISPLAY_HERTZ = 60.0
18 # When to consider a frame frozen (in VSYNC units):
19 # meaning 1 initial frame + 5 repeats of that frame.
20 FROZEN_THRESHOLD = 6
21 # Severity factor.
22 SEVERITY = 3
23
24
25 class WebMediaPlayerMsRenderingStats(object):
26 """Analyzes events of WebMediaPlayerMs type."""
27
28 def __init__(self, events):
29 """Save relevant events."""
30 self.relevant_events = events
31
32 def InferCadence(self):
33 """Calculate the apparent cadence of the rendering."""
34 # Term 'cadence' loosely used here for lack of a better word.
35 cadence = []
36 frame_persistence = 0
37 old_ideal_render = 0
38 for event in self.relevant_events:
39 if (event.args and 'Ideal Render Instant' in event.args
40 and event.args['Ideal Render Instant'] == old_ideal_render):
41 frame_persistence += 1
42 elif event.args and 'Ideal Render Instant' in event.args:
43 cadence.append(frame_persistence)
44 frame_persistence = 1
45 old_ideal_render = event.args['Ideal Render Instant']
46 cadence.append(frame_persistence)
47 cadence.pop(0)
48 return cadence
49
50 def Bucketize(self, cadence):
51 """Create distribution for the cadence frame display values."""
52 # If the overall display distribution is A1:A2:..:An,
53 # this will tell us how many times a frame
54 # stays displayed during Ak vsync duration (i.e. Ak/DISPLAY_HERTZ)
55 # also known as 'source to output' distribution.
56 bucket = {}
57 for ticks in cadence:
58 if ticks in bucket:
59 bucket[ticks] += 1
60 else:
61 bucket[ticks] = 1
62 return bucket
63
64 def InferFpsFromCadence(self, bucket):
65 """Calculate the apparent FPS from cadence pattern."""
66 # The mean ratio is the barycenter
67 weight = sum([bucket[ticks] for ticks in bucket])
qiangchen 2015/08/28 21:26:01 Possibly name this variable as num_frames.
cpaulin (no longer in chrome) 2015/09/16 22:57:04 Done.
68 population= sum([ticks * bucket[ticks] for ticks in bucket])
qiangchen 2015/08/28 21:26:01 Possibly name this variable as num_vsyncs.
cpaulin (no longer in chrome) 2015/09/16 22:57:04 Done.
69 mean_ratio = float(population) / weight
70 fps = DISPLAY_HERTZ / mean_ratio
71 return fps
72
73 def InferFrozenFramesEvents(self, bucket):
74 """Find evidence of frozen frames in distribution."""
75 # For simplicity we count as freezing the frames
76 # that appear at least five times in a row
77 # counted from 'Ideal Render Instant' perspective.
78 frozen_frames = []
79 for ticks in bucket:
80 if ticks >= FROZEN_THRESHOLD:
81 logging.error('%s frames not updated after %s vsyncs',
82 bucket[ticks], ticks)
83 frozen_frames.append(
84 {'frozen_frames' : ticks -1 ,
qiangchen 2015/08/28 21:26:01 Question: why we take ticks-1 here?
cpaulin (no longer in chrome) 2015/09/16 22:57:05 Because if you have 1 source frame and it displaye
85 'occurences' : bucket[ticks]})
86 return frozen_frames
87
88 def FrozenPenaltyWeight(self, number_frozen_frames):
89 """Returns the weighted penalty for a number of frozen frames."""
90 # As mentioned earlier, we count for frozen anything above 6 vsync
91 # display duration for the same 'Initial Render Instant'.
92 penalty = {
93 0 : 0,
94 1 : 0,
95 2 : 0,
96 3 : 0,
97 4 : 0,
98 5 : 1,
99 6 : 5,
100 7 : 15,
101 8 : 25
102 }
103 weight = penalty.get(number_frozen_frames,
104 8 * (number_frozen_frames - 4))
105 return weight
106
107 def InferTimeStats(self):
108 """Calculate time stamp stats for all events."""
109
110 cadence = self.InferCadence()
111 bucket = self.Bucketize(cadence)
112 fps = self.InferFpsFromCadence(bucket)
113 frozen_frames = self.InferFrozenFramesEvents(bucket)
114 # Drift time between Ideal Render Instant and Actual Render Begin.
115 drift_time = []
116 old_ideal_render = 0
117 discrepancy = []
118 index = 0
119 for event in self.relevant_events:
120 current_ideal_render = event.args['Ideal Render Instant']
121 if current_ideal_render == old_ideal_render:
122 continue
123 drift_time.append(
124 event.args['Actual Render Begin'] - current_ideal_render)
125 discrepancy.append(abs(current_ideal_render - old_ideal_render
126 - 1e6 / DISPLAY_HERTZ * cadence[index]))
127 old_ideal_render = current_ideal_render
128 index += 1
129 discrepancy.pop(0)
130 last_ideal_render = self.relevant_events[-1].args[
131 'Ideal Render Instant']
132 first_ideal_render = self.relevant_events[0].args[
133 'Ideal Render Instant']
134 rendering_length_error = 100.0 * (sum([x for x in discrepancy]) /
135 (last_ideal_render - first_ideal_render))
136 # Some stats on drift time.
137 mean_drift_time = numpy.mean(drift_time)
138 std_dev_drift_time = numpy.std(drift_time)
139 norm_drift_time = [abs(x - mean_drift_time) for x in drift_time]
140 # How many times is a frame later/earlier than T=2/DISPLAY_HERTZ.
141 # Time is in microseconds.
142 frames_severely_out_of_sync = len(
143 [x for x in norm_drift_time if abs(x) > 2e6 / DISPLAY_HERTZ])
qiangchen 2015/08/28 21:26:01 How about defining a variable say vsync_duration =
144 percent_badly_oos = (
145 100.0 * frames_severely_out_of_sync / len(norm_drift_time))
146 # How many times is a frame later/earlier than 1/DISPLAY_HERTZ.
147 frames_out_of_sync = len(
148 [x for x in norm_drift_time if abs(x) > 1e6 / (DISPLAY_HERTZ)])
149 percent_out_of_sync = (
150 100.0 * frames_out_of_sync / len(norm_drift_time))
151
152 frames_oos_only_once = frames_out_of_sync - frames_severely_out_of_sync
153 # For safety I don't use population = len(self.relevant_events) just
154 # in case other events are added later.
155 population = sum([n * bucket[n] for n in bucket])
qiangchen 2015/08/28 21:26:01 ditto
156 # Calculate smoothness metric.
157 # From the formula, we can see that smoothness score can be negative.
158 smoothness_score = 100.0 - 100.0*(frames_oos_only_once +
159 SEVERITY * frames_severely_out_of_sync) / len(norm_drift_time)
160 # Calculate freezing metric.
161 # Freezing metric can be negative if things are really bad.
162 freezing_score = 100.0
163 for frozen_report in frozen_frames:
164 weight = self.FrozenPenaltyWeight(frozen_report['frozen_frames'])
165 freezing_score -= (
166 100.0 * frozen_report['occurences'] / population * weight)
167
168 stats = {
169 'drift_time': drift_time,
170 'mean_drift_time' : mean_drift_time,
171 'std_dev_drift_time' : std_dev_drift_time,
172 'percent_badly_out_of_sync' : percent_badly_oos,
173 'percent_out_of_sync' : percent_out_of_sync,
174 'smoothness_score' : smoothness_score,
175 'freezing_score' : freezing_score,
176 'rendering_length_error' : rendering_length_error,
177 'fps' : fps,
178 'bucket' : bucket}
179 return stats
180
181
182 class WebRTCRendering(page_test.PageTest):
nednguyen 2015/09/02 16:26:46 Please don't add another page_test & use TimelineB
phoglund_chromium 2015/09/04 09:16:24 Right, and so most of this code should be a Metric
183 """Gathers WebRTC video rendering-related metrics on a page set."""
184
185 def __init__(self):
186 super(WebRTCRendering, self).__init__()
187 self._power_metric = None
188 self._webrtc_rendering_metric = None
189
190 def WillStartBrowser(self, platform):
191 self._power_metric = power.PowerMetric(platform)
192
193 def WillNavigateToPage(self, page, tab):
194 self._power_metric.Start(page, tab)
195
196 options = tracing_options.TracingOptions()
197 options.enable_chrome_trace = True
198 # FIXME: Remove the timeline category when impl-side painting is on
199 # everywhere.
200 category_filter = tracing_category_filter.TracingCategoryFilter('cc')
201 tab.browser.platform.tracing_controller.Start(options, category_filter)
202
203 def CustomizeBrowserOptions(self, options):
204 #options.AppendExtraBrowserArgs('--use-fake-device-for-media-stream')
205 options.AppendExtraBrowserArgs('--use-fake-ui-for-media-stream')
206 power.PowerMetric.CustomizeBrowserOptions(options)
207
208 def StopBrowserAfterPage(self, browser, page):
209 # Restart the browser after the last page in the pageset.
210 return True
211
212 def ValidateAndMeasurePage(self, page, tab, results):
213 timeline_data = tab.browser.platform.tracing_controller.Stop()
214 timeline_model = model.TimelineModel(timeline_data)
215 self._power_metric.Stop(page, tab)
216 self._power_metric.AddResults(tab, results)
217 rendering_events = timeline_model.GetAllEventsOfName(
218 'WebMediaPlayerMS::UpdateCurrentFrame')
219 if not rendering_events:
220 rendering_events = [{'args':{'Unkown':'Unknown'}}]
221 # TBD what to do next when no events exist?
222 event_parser = WebMediaPlayerMsRenderingStats(rendering_events)
223 rendering_stats = event_parser.InferTimeStats()
224 logging.info ("rendering stats : %s", rendering_stats)
225 results.AddValue(list_of_scalar_values.ListOfScalarValues(
226 results.current_page,
227 'WebRtcRendering_drift_time',
228 'usec',
229 rendering_stats['drift_time'],
230 important=True,
231 description='Drift time for a rendered frame'))
232
233 results.AddValue(scalar.ScalarValue(
234 results.current_page,
235 'WebRTCRendering_mean_drift_time',
236 'usec',
237 rendering_stats['mean_drift_time'],
238 important=True,
239 description='Mean drift time for frames'))
240
241 results.AddValue(scalar.ScalarValue(
242 results.current_page,
243 'WebRTCRendering_std_dev_drift_time',
244 'usec',
245 rendering_stats['std_dev_drift_time'],
246 important=True,
247 description='Standard deviation of drift time for frames'))
248
249 results.AddValue(scalar.ScalarValue(
250 results.current_page,
251 'WebRTCRendering_percent_badly_out_of_sync',
252 '%',
253 rendering_stats['percent_badly_out_of_sync'],
254 important=True,
255 description='Percentage of frame which drifted more than 2 VSYNC'))
256
257 results.AddValue(scalar.ScalarValue(
258 results.current_page,
259 'WebRTCRendering_percent_out_of_sync',
260 '%',
261 rendering_stats['percent_out_of_sync'],
262 important=True,
263 description='Percentage of frame which drifted more than 1 VSYNC'))
264
265 # make the output distribution a list since
266 # no facilities for dict values exist (yet)
267 bucket_list = []
268 for key, value in rendering_stats['bucket'].iteritems():
269 temp = "%s:%s" % (key,value)
270 bucket_list.append(temp)
271 results.AddValue(list_of_string_values.ListOfStringValues(
272 results.current_page,
273 'WebRtcRendering_bucket',
274 'frames:occurences',
275 bucket_list,
276 important=True,
277 description='Output distribution of frames'))
278
279 results.AddValue(scalar.ScalarValue(
280 results.current_page,
281 'WebRTCRendering_fps',
282 'FPS',
283 rendering_stats['fps'],
284 important=True,
285 description='Calculated Frame Rate of video rendering'))
286
287 results.AddValue(scalar.ScalarValue(
288 results.current_page,
289 'WebRTCRendering_smoothness_score',
290 '%',
291 rendering_stats['smoothness_score'],
292 important=True,
293 description='Smoothness score of rendering'))
294
295 results.AddValue(scalar.ScalarValue(
296 results.current_page,
297 'WebRTCRendering_freezing_score',
298 '%',
299 rendering_stats['freezing_score'],
300 important=True,
301 description='Freezing score of rendering'))
302
303 results.AddValue(scalar.ScalarValue(
304 results.current_page,
305 'WebRTCRendering_rendering_length_error',
306 '%',
307 rendering_stats['rendering_length_error'],
308 important=True,
309 description='Rendering length error rate'))
310
311 def CleanUpAfterPage(self, page, tab):
312 tracing_controller = tab.browser.platform.tracing_controller
313 if tracing_controller.is_tracing_running:
314 tracing_controller.Stop()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698