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