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 test, 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 power_status, power_utils 27from autotest_lib.client.cros import service_stopper 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 87 if self._GSC: 88 keyvals = self._GSC.get_memory_difference_keyvals() 89 if not self._test_power: 90 for key, val in keyvals.iteritems(): 91 self.output_perf_value( 92 description=key, 93 value=val, 94 units='bytes', 95 higher_is_better=False) 96 self.write_perf_keyval(keyvals) 97 super(graphics_WebGLAquarium, self).cleanup() 98 99 def run_fish_test(self, browser, test_url, num_fishes, perf_log=True): 100 """Run the test with the given number of fishes. 101 102 @param browser: The Browser object to run the test with. 103 @param test_url: The URL to the aquarium test site. 104 @param num_fishes: The number of fishes to run the test with. 105 @param perf_log: Report perf data only if it's set to True. 106 """ 107 # Create tab and load page. Set the number of fishes when page is fully 108 # loaded. 109 tab = browser.tabs.New() 110 tab.Navigate(test_url) 111 tab.Activate() 112 self.active_tab = tab 113 tab.WaitForDocumentReadyStateToBeComplete() 114 115 # Set the number of fishes when document finishes loading. Also reset 116 # our own FPS counter and start recording FPS and rendering time. 117 utils.wait_for_value( 118 lambda: tab.EvaluateJavaScript( 119 'if (document.readyState === "complete") {' 120 ' setSetting(document.getElementById("%s"), %d);' 121 ' g_crosFpsCounter.reset();' 122 ' true;' 123 '} else {' 124 ' false;' 125 '}' % self.test_settings[num_fishes] 126 ), 127 expected_value=True, 128 timeout_sec=30) 129 130 if self.kernel_sampler: 131 self.kernel_sampler.start_sampling_thread() 132 time.sleep(self.test_duration_secs) 133 if self.kernel_sampler: 134 self.kernel_sampler.stop_sampling_thread() 135 self.kernel_sampler.output_flip_stats('flip_stats_%d' % num_fishes) 136 self.flip_stats = {} 137 138 # Get average FPS and rendering time, then close the tab. 139 avg_fps = tab.EvaluateJavaScript('g_crosFpsCounter.getAvgFps();') 140 if math.isnan(float(avg_fps)): 141 raise error.TestFail('Failed: Could not get FPS count.') 142 143 avg_interframe_time = tab.EvaluateJavaScript( 144 'g_crosFpsCounter.getAvgInterFrameTime();') 145 avg_render_time = tab.EvaluateJavaScript( 146 'g_crosFpsCounter.getAvgRenderTime();') 147 std_interframe_time = tab.EvaluateJavaScript( 148 'g_crosFpsCounter.getStdInterFrameTime();') 149 std_render_time = tab.EvaluateJavaScript( 150 'g_crosFpsCounter.getStdRenderTime();') 151 self.perf_keyval['avg_fps_%04d_fishes' % num_fishes] = avg_fps 152 self.perf_keyval['avg_interframe_time_%04d_fishes' % num_fishes] = ( 153 avg_interframe_time) 154 self.perf_keyval['avg_render_time_%04d_fishes' % num_fishes] = ( 155 avg_render_time) 156 self.perf_keyval['std_interframe_time_%04d_fishes' % num_fishes] = ( 157 std_interframe_time) 158 self.perf_keyval['std_render_time_%04d_fishes' % num_fishes] = ( 159 std_render_time) 160 logging.info('%d fish(es): Average FPS = %f, ' 161 'average render time = %f', num_fishes, avg_fps, 162 avg_render_time) 163 if perf_log: 164 self.output_perf_value( 165 description='avg_fps_%04d_fishes' % num_fishes, 166 value=avg_fps, 167 units='fps', 168 higher_is_better=True) 169 170 def run_power_test(self, browser, test_url, ac_ok): 171 """Runs the webgl power consumption test and reports the perf results. 172 173 @param browser: The Browser object to run the test with. 174 @param test_url: The URL to the aquarium test site. 175 @param ac_ok: Boolean on whether its ok to have AC power supplied. 176 """ 177 178 self._backlight = power_utils.Backlight() 179 self._backlight.set_default() 180 181 self._service_stopper = service_stopper.ServiceStopper( 182 service_stopper.ServiceStopper.POWER_DRAW_SERVICES) 183 self._service_stopper.stop_services() 184 185 if not ac_ok: 186 self._power_status = power_status.get_status() 187 # Verify that we are running on battery and the battery is 188 # sufficiently charged. 189 self._power_status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN) 190 191 measurements = [ 192 power_status.SystemPower(self._power_status.battery_path) 193 ] 194 195 def get_power(): 196 power_logger = power_status.PowerLogger(measurements) 197 power_logger.start() 198 time.sleep(STABILIZATION_DURATION) 199 start_time = time.time() 200 time.sleep(MEASUREMENT_DURATION) 201 power_logger.checkpoint('result', start_time) 202 keyval = power_logger.calc() 203 logging.info('Power output %s', keyval) 204 return keyval['result_' + measurements[0].domain + '_pwr'] 205 206 self.run_fish_test(browser, test_url, 1000, perf_log=False) 207 if not ac_ok: 208 energy_rate = get_power() 209 # This is a power specific test so we are not capturing 210 # avg_fps and avg_render_time in this test. 211 self.perf_keyval[POWER_DESCRIPTION] = energy_rate 212 self.output_perf_value( 213 description=POWER_DESCRIPTION, 214 value=energy_rate, 215 units='W', 216 higher_is_better=False) 217 218 def exynos_sampler_callback(self, sampler_obj): 219 """Sampler callback function for ExynosSampler. 220 221 @param sampler_obj: The ExynosSampler object that invokes this callback 222 function. 223 """ 224 if sampler_obj.stopped: 225 return 226 227 with self.sampler_lock: 228 now = time.time() 229 results = {} 230 info_str = ['\nfb_id wait_kds flipped'] 231 for value in sampler_obj.frame_buffers.itervalues(): 232 results[value.fb] = {} 233 for state, stats in value.states.iteritems(): 234 results[value.fb][state] = (stats.avg, stats.stdev) 235 info_str.append('%s: %s %s' % (value.fb, 236 results[value.fb]['wait_kds'][0], 237 results[value.fb]['flipped'][0])) 238 results['avg_fps'] = self.active_tab.EvaluateJavaScript( 239 'g_crosFpsCounter.getAvgFps();') 240 results['avg_render_time'] = self.active_tab.EvaluateJavaScript( 241 'g_crosFpsCounter.getAvgRenderTime();') 242 self.active_tab.ExecuteJavaScript('g_crosFpsCounter.reset();') 243 info_str.append('avg_fps: %s, avg_render_time: %s' % 244 (results['avg_fps'], results['avg_render_time'])) 245 self.flip_stats[now] = results 246 logging.info('\n'.join(info_str)) 247 248 def exynos_output_flip_stats(self, file_name): 249 """Pageflip statistics output function for ExynosSampler. 250 251 @param file_name: The output file name. 252 """ 253 # output format: 254 # time fb_id avg_rendered avg_prepared avg_wait_kds avg_flipped 255 # std_rendered std_prepared std_wait_kds std_flipped 256 with open(file_name, 'w') as f: 257 for t in sorted(self.flip_stats.keys()): 258 if ('avg_fps' in self.flip_stats[t] and 259 'avg_render_time' in self.flip_stats[t]): 260 f.write('%s %s %s\n' % 261 (t, self.flip_stats[t]['avg_fps'], 262 self.flip_stats[t]['avg_render_time'])) 263 for fb, stats in self.flip_stats[t].iteritems(): 264 if not isinstance(fb, int): 265 continue 266 f.write('%s %s ' % (t, fb)) 267 f.write('%s %s %s %s ' % (stats['rendered'][0], 268 stats['prepared'][0], 269 stats['wait_kds'][0], 270 stats['flipped'][0])) 271 f.write('%s %s %s %s\n' % (stats['rendered'][1], 272 stats['prepared'][1], 273 stats['wait_kds'][1], 274 stats['flipped'][1])) 275 276 @graphics_utils.GraphicsTest.failure_report_decorator('graphics_WebGLAquarium') 277 def run_once(self, 278 test_duration_secs=30, 279 test_setting_num_fishes=(50, 1000), 280 power_test=False, 281 ac_ok=False): 282 """Find a browser with telemetry, and run the test. 283 284 @param test_duration_secs: The duration in seconds to run each scenario 285 for. 286 @param test_setting_num_fishes: A list of the numbers of fishes to 287 enable in the test. 288 @param power_test: Boolean on whether to run power_test 289 @param ac_ok: Boolean on whether its ok to have AC power supplied. 290 """ 291 self.test_duration_secs = test_duration_secs 292 self.test_setting_num_fishes = test_setting_num_fishes 293 294 with chrome.Chrome(logged_in=False, init_network_controller=True) as cr: 295 cr.browser.platform.SetHTTPServerDirectories(self.srcdir) 296 test_url = cr.browser.platform.http_server.UrlOf( 297 os.path.join(self.srcdir, 'aquarium.html')) 298 299 if not utils.wait_for_idle_cpu(60.0, 0.1): 300 if not utils.wait_for_idle_cpu(20.0, 0.2): 301 raise error.TestFail('Failed: Could not get idle CPU.') 302 if not utils.wait_for_cool_machine(): 303 raise error.TestFail('Failed: Could not get cold machine.') 304 if power_test: 305 self._test_power = True 306 self.run_power_test(cr.browser, test_url, ac_ok) 307 with self.sampler_lock: 308 self.active_tab.Close() 309 self.active_tab = None 310 else: 311 for n in self.test_setting_num_fishes: 312 self.run_fish_test(cr.browser, test_url, n) 313 # Do not close the tab when the sampler_callback is 314 # doing his work. 315 with self.sampler_lock: 316 self.active_tab.Close() 317 self.active_tab = None 318 self.write_perf_keyval(self.perf_keyval) 319