power_LoadTest.py revision 01d3c6edb4c352e83cd491bfdaf1aed5595727be
1# Copyright (c) 2010 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 5import logging, os, shutil, sys, time 6from autotest_lib.client.bin import site_backchannel, site_ui_test, site_login 7from autotest_lib.client.common_lib import error, site_httpd, \ 8 site_power_status, site_ui, utils 9 10sys.path.append(os.environ.get('SYSROOT', '') + '/usr/local/lib/flimflam/test') 11import flimflam 12 13 14params_dict = { 15 'test_time_ms': '_mseconds', 16 'should_scroll': '_should_scroll', 17 'should_scroll_up': '_should_scroll_up', 18 'scroll_loop': '_scroll_loop', 19 'scroll_interval_ms': '_scroll_interval_ms', 20 'scroll_by_pixels': '_scroll_by_pixels', 21} 22 23 24class power_LoadTest(site_ui_test.UITest): 25 version = 2 26 27 def ensure_login_complete(self): 28 """ 29 Override site_ui_test.UITest's ensure_login_complete. 30 Do not use auth server and local dns for our test. We need to be 31 able to reach the web. 32 """ 33 pass 34 35 def initialize(self, creds='$default', percent_initial_charge_min=None, 36 check_network=True, loop_time=3600, loop_count=1, 37 should_scroll='true', should_scroll_up='true', 38 scroll_loop='false', scroll_interval_ms='10000', 39 scroll_by_pixels='600', low_battery_threshold=3, 40 verbose=True, force_wifi=False, wifi_ap='', wifi_sec='none', 41 wifi_pw=''): 42 43 """ 44 percent_initial_charge_min: min battery charge at start of test 45 check_network: check that Ethernet interface is not running 46 loop_count: number of times to loop the test for 47 loop_time: length of time to run the test for in each loop 48 should_scroll: should the extension scroll pages 49 should_scroll_up: should scroll in up direction 50 scroll_loop: continue scrolling indefinitely 51 scroll_interval_ms: how often to scoll 52 scroll_by_pixels: number of pixels to scroll each time 53 """ 54 self._loop_time = loop_time 55 self._loop_count = loop_count 56 self._mseconds = self._loop_time * 1000 57 self._verbose = verbose 58 self._low_battery_threshold = low_battery_threshold 59 self._should_scroll = should_scroll 60 self._should_scroll_up = should_scroll_up 61 self._scroll_loop = scroll_loop 62 self._scroll_interval_ms = scroll_interval_ms 63 self._scroll_by_pixels = scroll_by_pixels 64 self._tmp_keyvals = {} 65 self._power_status = site_power_status.get_status() 66 self._json_path = None 67 68 # verify that initial conditions are met: 69 if self._power_status.linepower[0].online: 70 raise error.TestError( 71 'Running on AC power. Please remove AC power cable') 72 73 percent_initial_charge = self._percent_current_charge() 74 if percent_initial_charge_min and percent_initial_charge < \ 75 percent_initial_charge_min: 76 raise error.TestError('Initial charge (%f) less than min (%f)' 77 % (percent_initial_charge, percent_initial_charge_min)) 78 79 # If force wifi enabled, convert eth0 to backchannel and connect to the 80 # specified WiFi AP. 81 if force_wifi: 82 # If backchannel is already running, don't run it again. 83 if not site_backchannel.setup(): 84 raise error.TestError('Could not setup Backchannel network.') 85 86 # Note: FlimFlam is flaky after Backchannel setup sometimes. It may 87 # take several tries for WiFi to connect. More experimentation with 88 # the retry settings here may be necessary if this becomes a source 89 # of test flakiness in the future. 90 if not flimflam.FlimFlam().ConnectService(retry=True, 91 service_type='wifi', 92 ssid=wifi_ap, 93 security=wifi_sec, 94 passphrase=wifi_pw, 95 mode='managed')[0]: 96 raise error.TestError('Could not connect to WiFi network.') 97 98 if check_network and site_backchannel.is_network_iface_running('eth0'): 99 raise error.TestError( 100 'Ethernet interface is active. Please remove Ethernet cable') 101 102 # record the max backlight level 103 cmd = 'backlight-tool --get_max_brightness' 104 self._max_backlight = int(utils.system_output(cmd).rstrip()) 105 self._tmp_keyvals['level_backlight_max'] = self._max_backlight 106 107 # fix up file perms for the power test extension so that chrome 108 # can access it 109 os.system('chmod -R 755 %s' % self.bindir) 110 111 # TODO (bleung) : 112 # The new external extension packed crx means we can't pass params by 113 # modifying params.js 114 # Possible solution : 115 # - modify extension to not start until we poke it from the browser. 116 # then pass through URL. 117 118 # write test parameters to the power extension's params.js file 119 # self._ext_path = os.path.join(self.bindir, 'extension') 120 # self._write_ext_params() 121 122 # copy external_extensions.json to known location 123 self._json_path = os.path.join(self.bindir, '..') 124 shutil.copy(os.path.join(self.bindir, 'external_extensions.json'), 125 self._json_path) 126 127 # setup a HTTP Server to listen for status updates from the power 128 # test extension 129 self._testServer = site_httpd.HTTPListener(8001, docroot=self.bindir) 130 self._testServer.run() 131 132 # initialize various interesting power related stats 133 self._usb_stats = site_power_status.USBSuspendStats() 134 self._cpufreq_stats = site_power_status.CPUFreqStats() 135 self._cpuidle_stats = site_power_status.CPUIdleStats() 136 137 138 self._usb_stats.refresh() 139 self._cpufreq_stats.refresh() 140 self._cpuidle_stats.refresh() 141 self._power_status.refresh() 142 143 self._ah_charge_start = self._power_status.battery[0].charge_now 144 self._wh_energy_start = self._power_status.battery[0].energy 145 146 # from site_ui_test.UITest.initialize, sans authserver & local dns. 147 (self.username, self.password) = self._UITest__resolve_creds(creds) 148 149 def run_once(self): 150 151 t0 = time.time() 152 153 for i in range(self._loop_count): 154 # the power test extension will report its status here 155 latch = self._testServer.add_wait_url('/status') 156 157 if site_login.logged_in(): 158 site_login.attempt_logout() 159 # the act of logging in will launch chrome with external extension. 160 self.login(self.username, self.password) 161 162 # stop powerd 163 os.system('stop powerd') 164 165 # reset X settings since X gets restarted upon login 166 self._do_xset() 167 168 # reset backlight level since powerd might've modified it 169 # based on ambient light 170 self._set_backlight_level() 171 172 low_battery = self._do_wait(self._verbose, self._loop_time, 173 latch) 174 175 if self._verbose: 176 logging.debug('loop %d completed' % i) 177 178 if low_battery: 179 logging.info('Exiting due to low battery') 180 break 181 182 t1 = time.time() 183 self._tmp_keyvals['minutes_battery_life'] = (t1 - t0) / 60 184 185 186 def postprocess_iteration(self): 187 keyvals = {} 188 189 # refresh power related statistics 190 usb_stats = self._usb_stats.refresh() 191 cpufreq_stats = self._cpufreq_stats.refresh() 192 cpuidle_stats = self._cpuidle_stats.refresh() 193 194 # record percent time USB devices were not in suspended state 195 keyvals['percent_usb_active'] = usb_stats 196 197 # record percent time spent in each CPU C-state 198 for state in cpuidle_stats: 199 keyvals['percent_cpuidle_%s_time' % state] = cpuidle_stats[state] 200 201 # record percent time spent at each CPU frequency 202 for freq in cpufreq_stats: 203 keyvals['percent_cpufreq_%s_time' % freq] = cpufreq_stats[freq] 204 205 # record battery stats 206 keyvals['a_current_now'] = self._power_status.battery[0].current_now 207 keyvals['ah_charge_full'] = self._power_status.battery[0].charge_full 208 keyvals['ah_charge_full_design'] = \ 209 self._power_status.battery[0].charge_full_design 210 keyvals['ah_charge_start'] = self._ah_charge_start 211 keyvals['ah_charge_now'] = self._power_status.battery[0].charge_now 212 keyvals['ah_charge_used'] = keyvals['ah_charge_start'] - \ 213 keyvals['ah_charge_now'] 214 keyvals['wh_energy_start'] = self._wh_energy_start 215 keyvals['wh_energy_now'] = self._power_status.battery[0].energy 216 keyvals['wh_energy_used'] = keyvals['wh_energy_start'] - \ 217 keyvals['wh_energy_now'] 218 keyvals['v_voltage_min_design'] = \ 219 self._power_status.battery[0].voltage_min_design 220 keyvals['v_voltage_now'] = self._power_status.battery[0].voltage_now 221 222 keyvals.update(self._tmp_keyvals) 223 224 keyvals['a_current_rate'] = keyvals['ah_charge_used'] * 60 / \ 225 keyvals['minutes_battery_life'] 226 keyvals['w_energy_rate'] = keyvals['wh_energy_used'] * 60 / \ 227 keyvals['minutes_battery_life'] 228 229 self.write_perf_keyval(keyvals) 230 231 232 def cleanup(self): 233 # remove json file after test to stop external extension launch. 234 if self._json_path: 235 jsonfile = os.path.join(self._json_path, 'external_extensions.json') 236 if os.path.exists(jsonfile): 237 os.system('rm -f %s' % jsonfile) 238 # re-enable powerd 239 os.system('start powerd') 240 if site_login.logged_in(): 241 site_login.attempt_logout() 242 243 244 def _percent_current_charge(self): 245 return self._power_status.battery[0].charge_now * 100 / \ 246 self._power_status.battery[0].charge_full_design 247 248 249 def _write_ext_params(self): 250 data = '' 251 template= 'var %s = %s;\n' 252 for k in params_dict: 253 data += template % (k, getattr(self, params_dict[k])) 254 255 filename = os.path.join(self._ext_path, 'params.js') 256 utils.open_write_close(filename, data) 257 258 logging.debug('filename ' + filename) 259 logging.debug(data) 260 261 262 def _do_wait(self, verbose, seconds, latch): 263 latched = False 264 low_battery = False 265 total_time = seconds + 60 266 elapsed_time = 0 267 wait_time = 60 268 269 while elapsed_time < total_time: 270 time.sleep(wait_time) 271 elapsed_time += wait_time 272 273 self._power_status.refresh() 274 if verbose: 275 logging.debug('ah_charge_now %f' \ 276 % self._power_status.battery[0].charge_now) 277 logging.debug('w_energy_rate %f' \ 278 % self._power_status.battery[0].energy_rate) 279 280 low_battery = (self._percent_current_charge() < 281 self._low_battery_threshold) 282 283 latched = latch.is_set() 284 285 if latched or low_battery: 286 break 287 288 if latched: 289 # record chrome power extension stats 290 form_data = self._testServer.get_form_entries() 291 logging.debug(form_data) 292 for e in form_data: 293 key = 'ext_' + e 294 if key in self._tmp_keyvals: 295 self._tmp_keyvals[key] += form_data[e] 296 else: 297 self._tmp_keyvals[key] = form_data[e] 298 else: 299 logging.debug("Didn't get status back from power extension") 300 301 return low_battery 302 303 304 def _do_xset(self): 305 XSET = 'LD_LIBRARY_PATH=/usr/local/lib xset' 306 # Disable X screen saver 307 site_ui.xsystem('%s s 0 0' % XSET) 308 # Disable DPMS Standby/Suspend/Off 309 site_ui.xsystem('%s dpms 0 0 0' % XSET) 310 # Force monitor on 311 site_ui.xsystem('%s dpms force on' % XSET) 312 # Save off X settings 313 site_ui.xsystem('%s q' % XSET) 314 315 316 def _set_backlight_level(self): 317 # set backlight level to 40% of max 318 cmd = 'backlight-tool --set_brightness %d ' % ( 319 int(self._max_backlight * 0.4)) 320 os.system(cmd) 321 322 # record brightness level 323 cmd = 'backlight-tool --get_brightness' 324 level = int(utils.system_output(cmd).rstrip()) 325 logging.info('backlight level is %d' % level) 326 self._tmp_keyvals['level_backlight_current'] = level 327