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 3# found in the LICENSE file. 4 5import random 6import unittest 7 8from metrics.rendering_stats import RenderingStats 9from telemetry.core.timeline import model 10 11 12class MockTimer(object): 13 """A mock timer class which can generate random durations. 14 15 An instance of this class is used as a global timer to generate random 16 durations for stats and consistent timestamps for all mock trace events. 17 The unit of time is milliseconds. 18 """ 19 def __init__(self): 20 self.milliseconds = 0 21 22 def Get(self): 23 return self.milliseconds 24 25 def Advance(self, low=0, high=1): 26 delta = random.uniform(low, high) 27 self.milliseconds += delta 28 return delta 29 30 31class ReferenceRenderingStats(object): 32 """ Stores expected data for comparison with actual RenderingStats """ 33 def __init__(self): 34 self.frame_timestamps = [] 35 self.frame_times = [] 36 self.paint_time = [] 37 self.painted_pixel_count = [] 38 self.record_time = [] 39 self.recorded_pixel_count = [] 40 self.rasterize_time = [] 41 self.rasterized_pixel_count = [] 42 43 44def AddMainThreadRenderingStats(mock_timer, thread, first_frame, 45 ref_stats = None): 46 """ Adds a random main thread rendering stats event. 47 48 thread: The timeline model thread to which the event will be added. 49 first_frame: Is this the first frame within the bounds of an action? 50 ref_stats: A ReferenceRenderingStats object to record expected values. 51 """ 52 # Create randonm data and timestap for main thread rendering stats. 53 data = { 'frame_count': 0, 54 'paint_time': 0.0, 55 'painted_pixel_count': 0, 56 'record_time': mock_timer.Advance(2, 4) / 1000.0, 57 'recorded_pixel_count': 3000*3000 } 58 timestamp = mock_timer.Get() 59 60 # Add a slice with the event data to the given thread. 61 thread.PushCompleteSlice( 62 'benchmark', 'BenchmarkInstrumentation::MainThreadRenderingStats', 63 timestamp, duration=0.0, thread_timestamp=None, thread_duration=None, 64 args={'data': data}) 65 66 if not ref_stats: 67 return 68 69 # Add timestamp only if a frame was output 70 if data['frame_count'] == 1: 71 if not first_frame: 72 # Add frame_time if this is not the first frame in within the bounds of an 73 # action. 74 prev_timestamp = ref_stats.frame_timestamps[-1] 75 ref_stats.frame_times.append(round(timestamp - prev_timestamp, 2)) 76 ref_stats.frame_timestamps.append(timestamp) 77 78 ref_stats.paint_time.append(data['paint_time'] * 1000.0) 79 ref_stats.painted_pixel_count.append(data['painted_pixel_count']) 80 ref_stats.record_time.append(data['record_time'] * 1000.0) 81 ref_stats.recorded_pixel_count.append(data['recorded_pixel_count']) 82 83 84def AddImplThreadRenderingStats(mock_timer, thread, first_frame, 85 ref_stats = None): 86 """ Adds a random impl thread rendering stats event. 87 88 thread: The timeline model thread to which the event will be added. 89 first_frame: Is this the first frame within the bounds of an action? 90 ref_stats: A ReferenceRenderingStats object to record expected values. 91 """ 92 # Create randonm data and timestap for impl thread rendering stats. 93 data = { 'frame_count': 1, 94 'rasterize_time': mock_timer.Advance(5, 10) / 1000.0, 95 'rasterized_pixel_count': 1280*720 } 96 timestamp = mock_timer.Get() 97 98 # Add a slice with the event data to the given thread. 99 thread.PushCompleteSlice( 100 'benchmark', 'BenchmarkInstrumentation::ImplThreadRenderingStats', 101 timestamp, duration=0.0, thread_timestamp=None, thread_duration=None, 102 args={'data': data}) 103 104 if not ref_stats: 105 return 106 107 # Add timestamp only if a frame was output 108 if data['frame_count'] == 1: 109 if not first_frame: 110 # Add frame_time if this is not the first frame in within the bounds of an 111 # action. 112 prev_timestamp = ref_stats.frame_timestamps[-1] 113 ref_stats.frame_times.append(round(timestamp - prev_timestamp, 2)) 114 ref_stats.frame_timestamps.append(timestamp) 115 116 ref_stats.rasterize_time.append(data['rasterize_time'] * 1000.0) 117 ref_stats.rasterized_pixel_count.append(data['rasterized_pixel_count']) 118 119 120class RenderingStatsUnitTest(unittest.TestCase): 121 def testFromTimeline(self): 122 timeline = model.TimelineModel() 123 124 # Create a browser process and a renderer process, and a main thread and 125 # impl thread for each. 126 browser = timeline.GetOrCreateProcess(pid = 1) 127 browser_main = browser.GetOrCreateThread(tid = 11) 128 browser_compositor = browser.GetOrCreateThread(tid = 12) 129 renderer = timeline.GetOrCreateProcess(pid = 2) 130 renderer_main = renderer.GetOrCreateThread(tid = 21) 131 renderer_compositor = renderer.GetOrCreateThread(tid = 22) 132 133 timer = MockTimer() 134 ref_stats = ReferenceRenderingStats() 135 136 # Create 10 main and impl rendering stats events for Action A. 137 timer.Advance() 138 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '') 139 for i in xrange(0, 10): 140 first = (i == 0) 141 AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats) 142 AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats) 143 AddMainThreadRenderingStats(timer, browser_main, first, None) 144 AddImplThreadRenderingStats(timer, browser_compositor, first, None) 145 renderer_main.EndSlice(timer.Get()) 146 147 # Create 5 main and impl rendering stats events not within any action. 148 for i in xrange(0, 5): 149 first = (i == 0) 150 AddMainThreadRenderingStats(timer, renderer_main, first, None) 151 AddImplThreadRenderingStats(timer, renderer_compositor, first, None) 152 AddMainThreadRenderingStats(timer, browser_main, first, None) 153 AddImplThreadRenderingStats(timer, browser_compositor, first, None) 154 155 # Create 10 main and impl rendering stats events for Action B. 156 timer.Advance() 157 renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '') 158 for i in xrange(0, 10): 159 first = (i == 0) 160 AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats) 161 AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats) 162 AddMainThreadRenderingStats(timer, browser_main, first, None) 163 AddImplThreadRenderingStats(timer, browser_compositor, first, None) 164 renderer_main.EndSlice(timer.Get()) 165 166 # Create 10 main and impl rendering stats events for Action A. 167 timer.Advance() 168 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '') 169 for i in xrange(0, 10): 170 first = (i == 0) 171 AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats) 172 AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats) 173 AddMainThreadRenderingStats(timer, browser_main, first, None) 174 AddImplThreadRenderingStats(timer, browser_compositor, first, None) 175 renderer_main.EndSlice(timer.Get()) 176 177 renderer_main.FinalizeImport() 178 renderer_compositor.FinalizeImport() 179 180 timeline_markers = timeline.FindTimelineMarkers( 181 ['ActionA', 'ActionB', 'ActionA']) 182 stats = RenderingStats(renderer, timeline_markers) 183 184 # Compare rendering stats to reference. 185 self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps) 186 self.assertEquals(stats.frame_times, ref_stats.frame_times) 187 self.assertEquals(stats.rasterize_time, ref_stats.rasterize_time) 188 self.assertEquals(stats.rasterized_pixel_count, 189 ref_stats.rasterized_pixel_count) 190 self.assertEquals(stats.paint_time, ref_stats.paint_time) 191 self.assertEquals(stats.painted_pixel_count, ref_stats.painted_pixel_count) 192 self.assertEquals(stats.record_time, ref_stats.record_time) 193 self.assertEquals(stats.recorded_pixel_count, 194 ref_stats.recorded_pixel_count) 195