1# Copyright (c) 2013 The Chromium OS 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 5"""This is a client side WebGL aquarium test. 6 7Description of some of the test result output: 8 - interframe time: The time elapsed between two frames. It is the elapsed 9 time between two consecutive calls to the render() function. 10 - render time: The time it takes in Javascript to construct a frame and 11 submit all the GL commands. It is the time it takes for a render() 12 function call to complete. 13""" 14 15import logging 16import math 17import os 18import sampler 19import threading 20import time 21 22from autotest_lib.client.bin import utils 23from autotest_lib.client.common_lib import error 24from autotest_lib.client.common_lib.cros import chrome 25from autotest_lib.client.cros.graphics import graphics_utils 26from autotest_lib.client.cros import service_stopper 27from autotest_lib.client.cros.power import power_rapl, power_status, power_utils 28 29# Minimum battery charge percentage to run the test 30BATTERY_INITIAL_CHARGED_MIN = 10 31 32# Measurement duration in seconds. 33MEASUREMENT_DURATION = 30 34 35POWER_DESCRIPTION = 'avg_energy_rate_1000_fishes' 36 37# Time to exclude from calculation after playing a webgl demo [seconds]. 38STABILIZATION_DURATION = 10 39 40 41class graphics_WebGLAquarium(graphics_utils.GraphicsTest): 42 """WebGL aquarium graphics test.""" 43 version = 1 44 45 _backlight = None 46 _power_status = None 47 _service_stopper = None 48 _test_power = False 49 active_tab = None 50 flip_stats = {} 51 kernel_sampler = None 52 perf_keyval = {} 53 sampler_lock = None 54 test_duration_secs = 30 55 test_setting_num_fishes = 50 56 test_settings = { 57 50: ('setSetting2', 2), 58 1000: ('setSetting6', 6), 59 } 60 61 def setup(self): 62 tarball_path = os.path.join(self.bindir, 63 'webgl_aquarium_static.tar.bz2') 64 utils.extract_tarball_to_dir(tarball_path, self.srcdir) 65 66 def initialize(self): 67 super(graphics_WebGLAquarium, self).initialize() 68 self.sampler_lock = threading.Lock() 69 # TODO: Create samplers for other platforms (e.g. x86). 70 if utils.get_board().lower() in ['daisy', 'daisy_spring']: 71 # Enable ExynosSampler on Exynos platforms. The sampler looks for 72 # exynos-drm page flip states: 'wait_kds', 'rendered', 'prepared', 73 # and 'flipped' in kernel debugfs. 74 75 # Sample 3-second durtaion for every 5 seconds. 76 self.kernel_sampler = sampler.ExynosSampler(period=5, duration=3) 77 self.kernel_sampler.sampler_callback = self.exynos_sampler_callback 78 self.kernel_sampler.output_flip_stats = ( 79 self.exynos_output_flip_stats) 80 81 def cleanup(self): 82 if self._backlight: 83 self._backlight.restore() 84 if self._service_stopper: 85 self._service_stopper.restore_services() 86 super(graphics_WebGLAquarium, self).cleanup() 87 88 def run_fish_test(self, browser, test_url, num_fishes, perf_log=True): 89 """Run the test with the given number of fishes. 90 91 @param browser: The Browser object to run the test with. 92 @param test_url: The URL to the aquarium test site. 93 @param num_fishes: The number of fishes to run the test with. 94 @param perf_log: Report perf data only if it's set to True. 95 """ 96 # Create tab and load page. Set the number of fishes when page is fully 97 # loaded. 98 tab = browser.tabs.New() 99 tab.Navigate(test_url) 100 tab.Activate() 101 self.active_tab = tab 102 tab.WaitForDocumentReadyStateToBeComplete() 103 104 # Set the number of fishes when document finishes loading. Also reset 105 # our own FPS counter and start recording FPS and rendering time. 106 utils.wait_for_value( 107 lambda: tab.EvaluateJavaScript( 108 'if (document.readyState === "complete") {' 109 ' setSetting(document.getElementById("%s"), %d);' 110 ' g_crosFpsCounter.reset();' 111 ' true;' 112 '} else {' 113 ' false;' 114 '}' % self.test_settings[num_fishes] 115 ), 116 expected_value=True, 117 timeout_sec=30) 118 119 if self.kernel_sampler: 120 self.kernel_sampler.start_sampling_thread() 121 time.sleep(self.test_duration_secs) 122 if self.kernel_sampler: 123 self.kernel_sampler.stop_sampling_thread() 124 self.kernel_sampler.output_flip_stats('flip_stats_%d' % num_fishes) 125 self.flip_stats = {} 126 127 # Get average FPS and rendering time, then close the tab. 128 avg_fps = tab.EvaluateJavaScript('g_crosFpsCounter.getAvgFps();') 129 if math.isnan(float(avg_fps)): 130 raise error.TestFail('Failed: Could not get FPS count.') 131 132 avg_interframe_time = tab.EvaluateJavaScript( 133 'g_crosFpsCounter.getAvgInterFrameTime();') 134 avg_render_time = tab.EvaluateJavaScript( 135 'g_crosFpsCounter.getAvgRenderTime();') 136 std_interframe_time = tab.EvaluateJavaScript( 137 'g_crosFpsCounter.getStdInterFrameTime();') 138 std_render_time = tab.EvaluateJavaScript( 139 'g_crosFpsCounter.getStdRenderTime();') 140 self.perf_keyval['avg_fps_%04d_fishes' % num_fishes] = avg_fps 141 self.perf_keyval['avg_interframe_time_%04d_fishes' % num_fishes] = ( 142 avg_interframe_time) 143 self.perf_keyval['avg_render_time_%04d_fishes' % num_fishes] = ( 144 avg_render_time) 145 self.perf_keyval['std_interframe_time_%04d_fishes' % num_fishes] = ( 146 std_interframe_time) 147 self.perf_keyval['std_render_time_%04d_fishes' % num_fishes] = ( 148 std_render_time) 149 logging.info('%d fish(es): Average FPS = %f, ' 150 'average render time = %f', num_fishes, avg_fps, 151 avg_render_time) 152 153 if perf_log: 154 # Report frames per second to chromeperf/ dashboard. 155 self.output_perf_value( 156 description='avg_fps_%04d_fishes' % num_fishes, 157 value=avg_fps, 158 units='fps', 159 higher_is_better=True) 160 161 # Intel only: Record the power consumption for the next few seconds. 162 rapl_rate = power_rapl.get_rapl_measurement( 163 'rapl_%04d_fishes' % num_fishes) 164 # Remove entries that we don't care about. 165 rapl_rate = {key: rapl_rate[key] 166 for key in rapl_rate.keys() if key.endswith('pwr')} 167 # Report to chromeperf/ dashboard. 168 for key, values in rapl_rate.iteritems(): 169 self.output_perf_value( 170 description=key, 171 value=values, 172 units='W', 173 higher_is_better=False, 174 graph='rapl_power_consumption' 175 ) 176 177 def run_power_test(self, browser, test_url, ac_ok): 178 """Runs the webgl power consumption test and reports the perf results. 179 180 @param browser: The Browser object to run the test with. 181 @param test_url: The URL to the aquarium test site. 182 @param ac_ok: Boolean on whether its ok to have AC power supplied. 183 """ 184 185 self._backlight = power_utils.Backlight() 186 self._backlight.set_default() 187 188 self._service_stopper = service_stopper.ServiceStopper( 189 service_stopper.ServiceStopper.POWER_DRAW_SERVICES) 190 self._service_stopper.stop_services() 191 192 if not ac_ok: 193 self._power_status = power_status.get_status() 194 # Verify that we are running on battery and the battery is 195 # sufficiently charged. 196 self._power_status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN) 197 198 measurements = [ 199 power_status.SystemPower(self._power_status.battery_path) 200 ] 201 202 def get_power(): 203 power_logger = power_status.PowerLogger(measurements) 204 power_logger.start() 205 time.sleep(STABILIZATION_DURATION) 206 start_time = time.time() 207 time.sleep(MEASUREMENT_DURATION) 208 power_logger.checkpoint('result', start_time) 209 keyval = power_logger.calc() 210 logging.info('Power output %s', keyval) 211 return keyval['result_' + measurements[0].domain + '_pwr'] 212 213 self.run_fish_test(browser, test_url, 1000, perf_log=False) 214 if not ac_ok: 215 energy_rate = get_power() 216 # This is a power specific test so we are not capturing 217 # avg_fps and avg_render_time in this test. 218 self.perf_keyval[POWER_DESCRIPTION] = energy_rate 219 self.output_perf_value( 220 description=POWER_DESCRIPTION, 221 value=energy_rate, 222 units='W', 223 higher_is_better=False) 224 225 def exynos_sampler_callback(self, sampler_obj): 226 """Sampler callback function for ExynosSampler. 227 228 @param sampler_obj: The ExynosSampler object that invokes this callback 229 function. 230 """ 231 if sampler_obj.stopped: 232 return 233 234 with self.sampler_lock: 235 now = time.time() 236 results = {} 237 info_str = ['\nfb_id wait_kds flipped'] 238 for value in sampler_obj.frame_buffers.itervalues(): 239 results[value.fb] = {} 240 for state, stats in value.states.iteritems(): 241 results[value.fb][state] = (stats.avg, stats.stdev) 242 info_str.append('%s: %s %s' % (value.fb, 243 results[value.fb]['wait_kds'][0], 244 results[value.fb]['flipped'][0])) 245 results['avg_fps'] = self.active_tab.EvaluateJavaScript( 246 'g_crosFpsCounter.getAvgFps();') 247 results['avg_render_time'] = self.active_tab.EvaluateJavaScript( 248 'g_crosFpsCounter.getAvgRenderTime();') 249 self.active_tab.ExecuteJavaScript('g_crosFpsCounter.reset();') 250 info_str.append('avg_fps: %s, avg_render_time: %s' % 251 (results['avg_fps'], results['avg_render_time'])) 252 self.flip_stats[now] = results 253 logging.info('\n'.join(info_str)) 254 255 def exynos_output_flip_stats(self, file_name): 256 """Pageflip statistics output function for ExynosSampler. 257 258 @param file_name: The output file name. 259 """ 260 # output format: 261 # time fb_id avg_rendered avg_prepared avg_wait_kds avg_flipped 262 # std_rendered std_prepared std_wait_kds std_flipped 263 with open(file_name, 'w') as f: 264 for t in sorted(self.flip_stats.keys()): 265 if ('avg_fps' in self.flip_stats[t] and 266 'avg_render_time' in self.flip_stats[t]): 267 f.write('%s %s %s\n' % 268 (t, self.flip_stats[t]['avg_fps'], 269 self.flip_stats[t]['avg_render_time'])) 270 for fb, stats in self.flip_stats[t].iteritems(): 271 if not isinstance(fb, int): 272 continue 273 f.write('%s %s ' % (t, fb)) 274 f.write('%s %s %s %s ' % (stats['rendered'][0], 275 stats['prepared'][0], 276 stats['wait_kds'][0], 277 stats['flipped'][0])) 278 f.write('%s %s %s %s\n' % (stats['rendered'][1], 279 stats['prepared'][1], 280 stats['wait_kds'][1], 281 stats['flipped'][1])) 282 283 @graphics_utils.GraphicsTest.failure_report_decorator('graphics_WebGLAquarium') 284 def run_once(self, 285 test_duration_secs=30, 286 test_setting_num_fishes=(50, 1000), 287 power_test=False, 288 ac_ok=False): 289 """Find a browser with telemetry, and run the test. 290 291 @param test_duration_secs: The duration in seconds to run each scenario 292 for. 293 @param test_setting_num_fishes: A list of the numbers of fishes to 294 enable in the test. 295 @param power_test: Boolean on whether to run power_test 296 @param ac_ok: Boolean on whether its ok to have AC power supplied. 297 """ 298 self.test_duration_secs = test_duration_secs 299 self.test_setting_num_fishes = test_setting_num_fishes 300 301 with chrome.Chrome(logged_in=False, init_network_controller=True) as cr: 302 cr.browser.platform.SetHTTPServerDirectories(self.srcdir) 303 test_url = cr.browser.platform.http_server.UrlOf( 304 os.path.join(self.srcdir, 'aquarium.html')) 305 306 if not utils.wait_for_idle_cpu(60.0, 0.1): 307 if not utils.wait_for_idle_cpu(20.0, 0.2): 308 raise error.TestFail('Failed: Could not get idle CPU.') 309 if not utils.wait_for_cool_machine(): 310 raise error.TestFail('Failed: Could not get cold machine.') 311 if power_test: 312 self._test_power = True 313 self.run_power_test(cr.browser, test_url, ac_ok) 314 with self.sampler_lock: 315 self.active_tab.Close() 316 self.active_tab = None 317 else: 318 for n in self.test_setting_num_fishes: 319 self.run_fish_test(cr.browser, test_url, n) 320 # Do not close the tab when the sampler_callback is 321 # doing his work. 322 with self.sampler_lock: 323 self.active_tab.Close() 324 self.active_tab = None 325 self.write_perf_keyval(self.perf_keyval) 326