| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import random | 5 import random |
| 6 import unittest | 6 import unittest |
| 7 | 7 |
| 8 from metrics.rendering_stats import UI_COMP_NAME, BEGIN_COMP_NAME, END_COMP_NAME |
| 9 from metrics.rendering_stats import GetScrollInputLatencyEvents |
| 10 from metrics.rendering_stats import ComputeMouseWheelScrollLatency |
| 11 from metrics.rendering_stats import ComputeTouchScrollLatency |
| 8 from metrics.rendering_stats import RenderingStats | 12 from metrics.rendering_stats import RenderingStats |
| 9 import telemetry.core.timeline.bounds as timeline_bounds | 13 import telemetry.core.timeline.bounds as timeline_bounds |
| 10 from telemetry.core.timeline import model | 14 from telemetry.core.timeline import model |
| 15 import telemetry.core.timeline.async_slice as tracing_async_slice |
| 11 | 16 |
| 12 | 17 |
| 13 class MockTimer(object): | 18 class MockTimer(object): |
| 14 """A mock timer class which can generate random durations. | 19 """A mock timer class which can generate random durations. |
| 15 | 20 |
| 16 An instance of this class is used as a global timer to generate random | 21 An instance of this class is used as a global timer to generate random |
| 17 durations for stats and consistent timestamps for all mock trace events. | 22 durations for stats and consistent timestamps for all mock trace events. |
| 18 The unit of time is milliseconds. | 23 The unit of time is milliseconds. |
| 19 """ | 24 """ |
| 20 def __init__(self): | 25 def __init__(self): |
| (...skipping 23 matching lines...) Expand all Loading... |
| 44 def AppendNewRange(self): | 49 def AppendNewRange(self): |
| 45 self.frame_timestamps.append([]) | 50 self.frame_timestamps.append([]) |
| 46 self.frame_times.append([]) | 51 self.frame_times.append([]) |
| 47 self.paint_times.append([]) | 52 self.paint_times.append([]) |
| 48 self.painted_pixel_counts.append([]) | 53 self.painted_pixel_counts.append([]) |
| 49 self.record_times.append([]) | 54 self.record_times.append([]) |
| 50 self.recorded_pixel_counts.append([]) | 55 self.recorded_pixel_counts.append([]) |
| 51 self.rasterize_times.append([]) | 56 self.rasterize_times.append([]) |
| 52 self.rasterized_pixel_counts.append([]) | 57 self.rasterized_pixel_counts.append([]) |
| 53 | 58 |
| 59 class ReferenceInputLatencyStats(object): |
| 60 """ Stores expected data for comparison with actual input latency stats """ |
| 61 def __init__(self): |
| 62 self.mouse_wheel_scroll_latency = [] |
| 63 self.touch_scroll_latency = [] |
| 64 self.mouse_wheel_scroll_events = [] |
| 65 self.touch_scroll_events = [] |
| 54 | 66 |
| 55 def AddMainThreadRenderingStats(mock_timer, thread, first_frame, | 67 def AddMainThreadRenderingStats(mock_timer, thread, first_frame, |
| 56 ref_stats = None): | 68 ref_stats = None): |
| 57 """ Adds a random main thread rendering stats event. | 69 """ Adds a random main thread rendering stats event. |
| 58 | 70 |
| 59 thread: The timeline model thread to which the event will be added. | 71 thread: The timeline model thread to which the event will be added. |
| 60 first_frame: Is this the first frame within the bounds of an action? | 72 first_frame: Is this the first frame within the bounds of an action? |
| 61 ref_stats: A ReferenceRenderingStats object to record expected values. | 73 ref_stats: A ReferenceRenderingStats object to record expected values. |
| 62 """ | 74 """ |
| 63 # Create randonm data and timestap for main thread rendering stats. | 75 # Create randonm data and timestap for main thread rendering stats. |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 121 # Add frame_time if this is not the first frame in within the bounds of an | 133 # Add frame_time if this is not the first frame in within the bounds of an |
| 122 # action. | 134 # action. |
| 123 prev_timestamp = ref_stats.frame_timestamps[-1][-1] | 135 prev_timestamp = ref_stats.frame_timestamps[-1][-1] |
| 124 ref_stats.frame_times[-1].append(round(timestamp - prev_timestamp, 2)) | 136 ref_stats.frame_times[-1].append(round(timestamp - prev_timestamp, 2)) |
| 125 ref_stats.frame_timestamps[-1].append(timestamp) | 137 ref_stats.frame_timestamps[-1].append(timestamp) |
| 126 | 138 |
| 127 ref_stats.rasterize_times[-1].append(data['rasterize_time'] * 1000.0) | 139 ref_stats.rasterize_times[-1].append(data['rasterize_time'] * 1000.0) |
| 128 ref_stats.rasterized_pixel_counts[-1].append(data['rasterized_pixel_count']) | 140 ref_stats.rasterized_pixel_counts[-1].append(data['rasterized_pixel_count']) |
| 129 | 141 |
| 130 | 142 |
| 143 def AddInputLatencyStats(mock_timer, input_type, start_thread, end_thread, |
| 144 ref_latency_stats = None): |
| 145 """ Adds a random input latency stats event. |
| 146 |
| 147 input_type: The input type for which the latency slice is generated. |
| 148 start_thread: The start thread on which the async slice is added. |
| 149 end_thread: The end thread on which the async slice is ended. |
| 150 ref_latency_stats: A ReferenceInputLatencyStats object for expected values. |
| 151 """ |
| 152 |
| 153 mock_timer.Advance() |
| 154 ui_comp_time = mock_timer.Get() * 1000.0 |
| 155 mock_timer.Advance() |
| 156 begin_comp_time = mock_timer.Get() * 1000.0 |
| 157 mock_timer.Advance(10, 20) |
| 158 end_comp_time = mock_timer.Get() * 1000.0 |
| 159 |
| 160 data = { UI_COMP_NAME: {'time': ui_comp_time}, |
| 161 BEGIN_COMP_NAME: {'time': begin_comp_time}, |
| 162 END_COMP_NAME: {'time': end_comp_time} } |
| 163 |
| 164 timestamp = mock_timer.Get() |
| 165 |
| 166 async_slice = tracing_async_slice.AsyncSlice( |
| 167 'benchmark', 'InputLatency', timestamp) |
| 168 |
| 169 async_sub_slice = tracing_async_slice.AsyncSlice( |
| 170 'benchmark', 'InputLatency', timestamp) |
| 171 async_sub_slice.args = {'data': data, 'step': input_type} |
| 172 async_sub_slice.parent_slice = async_slice |
| 173 async_sub_slice.start_thread = start_thread |
| 174 async_sub_slice.end_thread = end_thread |
| 175 |
| 176 async_slice.sub_slices.append(async_sub_slice) |
| 177 async_slice.start_thread = start_thread |
| 178 async_slice.end_thread = end_thread |
| 179 start_thread.AddAsyncSlice(async_slice) |
| 180 |
| 181 if not ref_latency_stats: |
| 182 return |
| 183 |
| 184 if input_type == 'MouseWheel': |
| 185 ref_latency_stats.mouse_wheel_scroll_events.append(async_sub_slice) |
| 186 ref_latency_stats.mouse_wheel_scroll_latency.append( |
| 187 (data[END_COMP_NAME]['time'] - data[BEGIN_COMP_NAME]['time']) / 1000.0) |
| 188 |
| 189 |
| 190 if input_type == 'GestureScrollUpdate': |
| 191 ref_latency_stats.touch_scroll_events.append(async_sub_slice) |
| 192 ref_latency_stats.touch_scroll_latency.append( |
| 193 (data[END_COMP_NAME]['time'] - data[UI_COMP_NAME]['time']) / 1000.0) |
| 194 |
| 131 class RenderingStatsUnitTest(unittest.TestCase): | 195 class RenderingStatsUnitTest(unittest.TestCase): |
| 132 def testFromTimeline(self): | 196 def testFromTimeline(self): |
| 133 timeline = model.TimelineModel() | 197 timeline = model.TimelineModel() |
| 134 | 198 |
| 135 # Create a browser process and a renderer process, and a main thread and | 199 # Create a browser process and a renderer process, and a main thread and |
| 136 # impl thread for each. | 200 # impl thread for each. |
| 137 browser = timeline.GetOrCreateProcess(pid = 1) | 201 browser = timeline.GetOrCreateProcess(pid = 1) |
| 138 browser_main = browser.GetOrCreateThread(tid = 11) | 202 browser_main = browser.GetOrCreateThread(tid = 11) |
| 139 browser_compositor = browser.GetOrCreateThread(tid = 12) | 203 browser_compositor = browser.GetOrCreateThread(tid = 12) |
| 140 renderer = timeline.GetOrCreateProcess(pid = 2) | 204 renderer = timeline.GetOrCreateProcess(pid = 2) |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 188 AddImplThreadRenderingStats(timer, browser_compositor, first, None) | 252 AddImplThreadRenderingStats(timer, browser_compositor, first, None) |
| 189 renderer_main.EndSlice(timer.Get()) | 253 renderer_main.EndSlice(timer.Get()) |
| 190 | 254 |
| 191 renderer_main.FinalizeImport() | 255 renderer_main.FinalizeImport() |
| 192 renderer_compositor.FinalizeImport() | 256 renderer_compositor.FinalizeImport() |
| 193 | 257 |
| 194 timeline_markers = timeline.FindTimelineMarkers( | 258 timeline_markers = timeline.FindTimelineMarkers( |
| 195 ['ActionA', 'ActionB', 'ActionA']) | 259 ['ActionA', 'ActionB', 'ActionA']) |
| 196 timeline_ranges = [ timeline_bounds.Bounds.CreateFromEvent(marker) | 260 timeline_ranges = [ timeline_bounds.Bounds.CreateFromEvent(marker) |
| 197 for marker in timeline_markers ] | 261 for marker in timeline_markers ] |
| 198 stats = RenderingStats(renderer, timeline_ranges) | 262 stats = RenderingStats(renderer, browser, timeline_ranges) |
| 199 | 263 |
| 200 # Compare rendering stats to reference. | 264 # Compare rendering stats to reference. |
| 201 self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps) | 265 self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps) |
| 202 self.assertEquals(stats.frame_times, ref_stats.frame_times) | 266 self.assertEquals(stats.frame_times, ref_stats.frame_times) |
| 203 self.assertEquals(stats.rasterize_times, ref_stats.rasterize_times) | 267 self.assertEquals(stats.rasterize_times, ref_stats.rasterize_times) |
| 204 self.assertEquals(stats.rasterized_pixel_counts, | 268 self.assertEquals(stats.rasterized_pixel_counts, |
| 205 ref_stats.rasterized_pixel_counts) | 269 ref_stats.rasterized_pixel_counts) |
| 206 self.assertEquals(stats.paint_times, ref_stats.paint_times) | 270 self.assertEquals(stats.paint_times, ref_stats.paint_times) |
| 207 self.assertEquals(stats.painted_pixel_counts, | 271 self.assertEquals(stats.painted_pixel_counts, |
| 208 ref_stats.painted_pixel_counts) | 272 ref_stats.painted_pixel_counts) |
| 209 self.assertEquals(stats.record_times, ref_stats.record_times) | 273 self.assertEquals(stats.record_times, ref_stats.record_times) |
| 210 self.assertEquals(stats.recorded_pixel_counts, | 274 self.assertEquals(stats.recorded_pixel_counts, |
| 211 ref_stats.recorded_pixel_counts) | 275 ref_stats.recorded_pixel_counts) |
| 276 |
| 277 def testScrollLatencyFromTimeline(self): |
| 278 timeline = model.TimelineModel() |
| 279 |
| 280 # Create a browser process and a renderer process. |
| 281 browser = timeline.GetOrCreateProcess(pid = 1) |
| 282 browser_main = browser.GetOrCreateThread(tid = 11) |
| 283 renderer = timeline.GetOrCreateProcess(pid = 2) |
| 284 renderer_main = renderer.GetOrCreateThread(tid = 21) |
| 285 |
| 286 timer = MockTimer() |
| 287 ref_latency_stats = ReferenceInputLatencyStats() |
| 288 |
| 289 # Create 10 input latency stats events for Action A. |
| 290 timer.Advance() |
| 291 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '') |
| 292 for _ in xrange(0, 10): |
| 293 AddInputLatencyStats(timer, 'MouseWheel', browser_main, |
| 294 renderer_main, ref_latency_stats) |
| 295 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main, |
| 296 renderer_main, ref_latency_stats) |
| 297 renderer_main.EndSlice(timer.Get()) |
| 298 |
| 299 # Create 5 input latency stats events not within any action. |
| 300 for _ in xrange(0, 5): |
| 301 AddInputLatencyStats(timer, 'MouseWheel', browser_main, |
| 302 renderer_main, None) |
| 303 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main, |
| 304 renderer_main, None) |
| 305 |
| 306 # Create 10 input latency stats events for Action B. |
| 307 timer.Advance() |
| 308 renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '') |
| 309 for _ in xrange(0, 10): |
| 310 AddInputLatencyStats(timer, 'MouseWheel', browser_main, |
| 311 renderer_main, ref_latency_stats) |
| 312 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main, |
| 313 renderer_main, ref_latency_stats) |
| 314 renderer_main.EndSlice(timer.Get()) |
| 315 |
| 316 # Create 10 input latency stats events for Action A. |
| 317 timer.Advance() |
| 318 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '') |
| 319 for _ in xrange(0, 10): |
| 320 AddInputLatencyStats(timer, 'MouseWheel', browser_main, |
| 321 renderer_main, ref_latency_stats) |
| 322 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main, |
| 323 renderer_main, ref_latency_stats) |
| 324 renderer_main.EndSlice(timer.Get()) |
| 325 |
| 326 browser_main.FinalizeImport() |
| 327 renderer_main.FinalizeImport() |
| 328 |
| 329 mouse_wheel_scroll_events = [] |
| 330 touch_scroll_events = [] |
| 331 |
| 332 timeline_markers = timeline.FindTimelineMarkers( |
| 333 ['ActionA', 'ActionB', 'ActionA']) |
| 334 for timeline_range in [ timeline_bounds.Bounds.CreateFromEvent(marker) |
| 335 for marker in timeline_markers ]: |
| 336 if timeline_range.is_empty: |
| 337 continue |
| 338 tmp_mouse_events, tmp_touch_events = GetScrollInputLatencyEvents( |
| 339 browser, timeline_range) |
| 340 mouse_wheel_scroll_events.extend(tmp_mouse_events) |
| 341 touch_scroll_events.extend(tmp_touch_events) |
| 342 |
| 343 self.assertEquals(mouse_wheel_scroll_events, |
| 344 ref_latency_stats.mouse_wheel_scroll_events) |
| 345 self.assertEquals(touch_scroll_events, |
| 346 ref_latency_stats.touch_scroll_events) |
| 347 self.assertEquals(ComputeMouseWheelScrollLatency(mouse_wheel_scroll_events), |
| 348 ref_latency_stats.mouse_wheel_scroll_latency) |
| 349 self.assertEquals(ComputeTouchScrollLatency(touch_scroll_events), |
| 350 ref_latency_stats.touch_scroll_latency) |
| OLD | NEW |