1 # Copyright (c) 2012 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 import collections 6 import json 7 import logging 8 import numpy 9 import os 10 import time 11 12 from autotest_lib.client.bin import utils 13 from autotest_lib.client.common_lib import error 14 from autotest_lib.client.common_lib import utils as _utils 15 from autotest_lib.client.common_lib.cros import arc 16 from autotest_lib.client.common_lib.cros import arc_common 17 from autotest_lib.client.common_lib.cros import chrome 18 from autotest_lib.client.common_lib.cros import power_load_util 19 from autotest_lib.client.common_lib.cros.network import interface 20 from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes 21 from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types 22 from autotest_lib.client.cros import backchannel, httpd 23 from autotest_lib.client.cros import memory_bandwidth_logger 24 from autotest_lib.client.cros import service_stopper 25 from autotest_lib.client.cros.audio import audio_helper 26 from autotest_lib.client.cros.networking import wifi_proxy 27 from autotest_lib.client.cros.power import power_dashboard 28 from autotest_lib.client.cros.power import power_rapl 29 from autotest_lib.client.cros.power import power_status 30 from autotest_lib.client.cros.power import power_utils 31 from telemetry.core import exceptions 32 33 params_dict = { 34 'test_time_ms': '_mseconds', 35 'should_scroll': '_should_scroll', 36 'should_scroll_up': '_should_scroll_up', 37 'scroll_loop': '_scroll_loop', 38 'scroll_interval_ms': '_scroll_interval_ms', 39 'scroll_by_pixels': '_scroll_by_pixels', 40 'tasks': '_tasks', 41 } 42 43 class power_LoadTest(arc.ArcTest): 44 """test class""" 45 version = 2 46 47 def initialize(self, percent_initial_charge_min=None, 48 check_network=True, loop_time=3600, loop_count=1, 49 should_scroll='true', should_scroll_up='true', 50 scroll_loop='false', scroll_interval_ms='10000', 51 scroll_by_pixels='600', test_low_batt_p=3, 52 verbose=True, force_wifi=False, wifi_ap='', wifi_sec='none', 53 wifi_pw='', wifi_timeout=60, tasks='', 54 volume_level=10, mic_gain=10, low_batt_margin_p=2, 55 ac_ok=False, log_mem_bandwidth=False, gaia_login=None, 56 force_discharge=False, pdash_note=''): 57 """ 58 percent_initial_charge_min: min battery charge at start of test 59 check_network: check that Ethernet interface is not running 60 loop_time: length of time to run the test for in each loop 61 loop_count: number of times to loop the test for 62 should_scroll: should the extension scroll pages 63 should_scroll_up: should scroll in up direction 64 scroll_loop: continue scrolling indefinitely 65 scroll_interval_ms: how often to scoll 66 scroll_by_pixels: number of pixels to scroll each time 67 test_low_batt_p: percent battery at which test should stop 68 verbose: add more logging information 69 force_wifi: should we force to test to run on wifi 70 wifi_ap: the name (ssid) of the wifi access point 71 wifi_sec: the type of security for the wifi ap 72 wifi_pw: password for the wifi ap 73 wifi_timeout: The timeout for wifi configuration 74 volume_level: percent audio volume level 75 mic_gain: percent audio microphone gain level 76 low_batt_margin_p: percent low battery margin to be added to 77 sys_low_batt_p to guarantee test completes prior to powerd shutdown 78 ac_ok: boolean to allow running on AC 79 log_mem_bandwidth: boolean to log memory bandwidth during the test 80 gaia_login: whether real GAIA login should be attempted. If 'None' 81 (default) then boolean is determined from URL. 82 force_discharge: boolean of whether to tell ec to discharge battery even 83 when the charger is plugged in. 84 pdash_note: note of the current run to send to power dashboard. 85 """ 86 self._backlight = None 87 self._services = None 88 self._browser = None 89 self._loop_time = loop_time 90 self._loop_count = loop_count 91 self._mseconds = self._loop_time * 1000 92 self._verbose = verbose 93 94 self._sys_low_batt_p = 0. 95 self._sys_low_batt_s = 0. 96 self._test_low_batt_p = test_low_batt_p 97 self._should_scroll = should_scroll 98 self._should_scroll_up = should_scroll_up 99 self._scroll_loop = scroll_loop 100 self._scroll_interval_ms = scroll_interval_ms 101 self._scroll_by_pixels = scroll_by_pixels 102 self._tmp_keyvals = {} 103 self._power_status = None 104 self._force_wifi = force_wifi 105 self._testServer = None 106 self._tasks = tasks.replace(' ','') 107 self._backchannel = None 108 self._shill_proxy = None 109 self._volume_level = volume_level 110 self._mic_gain = mic_gain 111 self._ac_ok = ac_ok 112 self._log_mem_bandwidth = log_mem_bandwidth 113 self._wait_time = 60 114 self._stats = collections.defaultdict(list) 115 self._force_discharge = force_discharge 116 self._pdash_note = pdash_note 117 118 if not power_utils.has_battery(): 119 if ac_ok and (power_utils.has_powercap_support() or 120 power_utils.has_rapl_support()): 121 logging.info("Device has no battery but has powercap data.") 122 else: 123 rsp = "Skipping test for device without battery and powercap." 124 raise error.TestNAError(rsp) 125 self._power_status = power_status.get_status() 126 self._tmp_keyvals['b_on_ac'] = self._power_status.on_ac() 127 128 self._gaia_login = gaia_login 129 if gaia_login is None: 130 self._gaia_login = power_load_util.use_gaia_login() 131 132 self._username = power_load_util.get_username() 133 self._password = power_load_util.get_password() 134 135 if not ac_ok: 136 self._power_status.assert_battery_state(percent_initial_charge_min) 137 138 if force_discharge: 139 if not power_utils.charge_control_by_ectool(False): 140 raise error.TestError('Could not run battery force discharge.') 141 142 # If force wifi enabled, convert eth0 to backchannel and connect to the 143 # specified WiFi AP. 144 if self._force_wifi: 145 sec_config = None 146 # TODO(dbasehore): Fix this when we get a better way of figuring out 147 # the wifi security configuration. 148 if wifi_sec == 'rsn' or wifi_sec == 'wpa': 149 sec_config = xmlrpc_security_types.WPAConfig( 150 psk=wifi_pw, 151 wpa_mode=xmlrpc_security_types.WPAConfig.MODE_PURE_WPA2, 152 wpa2_ciphers= 153 [xmlrpc_security_types.WPAConfig.CIPHER_CCMP]) 154 wifi_config = xmlrpc_datatypes.AssociationParameters( 155 ssid=wifi_ap, security_config=sec_config, 156 configuration_timeout=wifi_timeout) 157 # If backchannel is already running, don't run it again. 158 self._backchannel = backchannel.Backchannel() 159 if not self._backchannel.setup(): 160 raise error.TestError('Could not setup Backchannel network.') 161 162 self._shill_proxy = wifi_proxy.WifiProxy() 163 self._shill_proxy.remove_all_wifi_entries() 164 for i in xrange(1,4): 165 raw_output = self._shill_proxy.connect_to_wifi_network( 166 wifi_config.ssid, 167 wifi_config.security, 168 wifi_config.security_parameters, 169 wifi_config.save_credentials, 170 station_type=wifi_config.station_type, 171 hidden_network=wifi_config.is_hidden, 172 discovery_timeout_seconds= 173 wifi_config.discovery_timeout, 174 association_timeout_seconds= 175 wifi_config.association_timeout, 176 configuration_timeout_seconds= 177 wifi_config.configuration_timeout * i) 178 result = xmlrpc_datatypes.AssociationResult. \ 179 from_dbus_proxy_output(raw_output) 180 if result.success: 181 break 182 logging.warn('wifi connect: disc:%d assoc:%d config:%d fail:%s', 183 result.discovery_time, result.association_time, 184 result.configuration_time, result.failure_reason) 185 else: 186 raise error.TestError('Could not connect to WiFi network.') 187 188 else: 189 # Find all wired ethernet interfaces. 190 ifaces = [ iface for iface in interface.get_interfaces() 191 if (not iface.is_wifi_device() and 192 iface.name.startswith('eth')) ] 193 logging.debug(str([iface.name for iface in ifaces])) 194 for iface in ifaces: 195 if check_network and iface.is_lower_up: 196 raise error.TestError('Ethernet interface is active. ' + 197 'Please remove Ethernet cable') 198 199 # record the max backlight level 200 self._backlight = power_utils.Backlight() 201 self._tmp_keyvals['level_backlight_max'] = \ 202 self._backlight.get_max_level() 203 204 self._services = service_stopper.ServiceStopper( 205 service_stopper.ServiceStopper.POWER_DRAW_SERVICES) 206 self._services.stop_services() 207 208 self._detachable_handler = power_utils.BaseActivitySimulator() 209 210 # fix up file perms for the power test extension so that chrome 211 # can access it 212 os.system('chmod -R 755 %s' % self.bindir) 213 214 # setup a HTTP Server to listen for status updates from the power 215 # test extension 216 self._testServer = httpd.HTTPListener(8001, docroot=self.bindir) 217 self._testServer.run() 218 219 # initialize various interesting power related stats 220 self._statomatic = power_status.StatoMatic() 221 222 self._power_status.refresh() 223 self._sys_low_batt_p = float(utils.system_output( 224 'check_powerd_config --low_battery_shutdown_percent')) 225 self._sys_low_batt_s = int(utils.system_output( 226 'check_powerd_config --low_battery_shutdown_time')) 227 228 if self._sys_low_batt_p and self._sys_low_batt_s: 229 raise error.TestError( 230 "Low battery percent and seconds are non-zero.") 231 232 min_low_batt_p = min(self._sys_low_batt_p + low_batt_margin_p, 100) 233 if self._sys_low_batt_p and (min_low_batt_p > self._test_low_batt_p): 234 logging.warning("test low battery threshold is below system " + 235 "low battery requirement. Setting to %f", 236 min_low_batt_p) 237 self._test_low_batt_p = min_low_batt_p 238 239 if self._power_status.battery: 240 self._ah_charge_start = self._power_status.battery[0].charge_now 241 self._wh_energy_start = self._power_status.battery[0].energy 242 243 244 def run_once(self): 245 """Test main loop.""" 246 t0 = time.time() 247 248 # record the PSR related info. 249 psr = power_utils.DisplayPanelSelfRefresh(init_time=t0) 250 251 try: 252 self._keyboard_backlight = power_utils.KbdBacklight() 253 self._set_keyboard_backlight_level() 254 except power_utils.KbdBacklightException as e: 255 logging.info("Assuming no keyboard backlight due to :: %s", str(e)) 256 self._keyboard_backlight = None 257 258 measurements = [] 259 if self._power_status.battery: 260 measurements += \ 261 [power_status.SystemPower(self._power_status.battery_path)] 262 if power_utils.has_powercap_support(): 263 measurements += power_rapl.create_powercap() 264 elif power_utils.has_rapl_support(): 265 measurements += power_rapl.create_rapl() 266 self._checkpoint_logger = power_status.CheckpointLogger() 267 self._plog = power_status.PowerLogger(measurements, 268 seconds_period=20, 269 checkpoint_logger=self._checkpoint_logger) 270 self._tlog = power_status.TempLogger([], 271 seconds_period=20, 272 checkpoint_logger=self._checkpoint_logger) 273 self._clog = power_status.CPUStatsLogger( 274 seconds_period=20, 275 checkpoint_logger=self._checkpoint_logger) 276 self._meas_logs = [self._plog, self._tlog, self._clog] 277 for log in self._meas_logs: 278 log.start() 279 if self._log_mem_bandwidth: 280 self._mlog = memory_bandwidth_logger.MemoryBandwidthLogger( 281 raw=False, seconds_period=2) 282 self._mlog.start() 283 284 # record start time and end time for each task 285 self._task_tracker = [] 286 287 ext_path = os.path.join(os.path.dirname(__file__), 'extension') 288 self._tmp_keyvals['username'] = self._username 289 290 arc_mode = arc_common.ARC_MODE_DISABLED 291 if utils.is_arc_available(): 292 arc_mode = arc_common.ARC_MODE_ENABLED 293 294 try: 295 self._browser = chrome.Chrome(extension_paths=[ext_path], 296 gaia_login=self._gaia_login, 297 username=self._username, 298 password=self._password, 299 arc_mode=arc_mode) 300 except exceptions.LoginException: 301 # already failed guest login 302 if not self._gaia_login: 303 raise 304 self._gaia_login = False 305 logging.warn("Unable to use GAIA acct %s. Using GUEST instead.\n", 306 self._username) 307 self._browser = chrome.Chrome(extension_paths=[ext_path], 308 gaia_login=self._gaia_login) 309 if not self._gaia_login: 310 self._tmp_keyvals['username'] = 'GUEST' 311 312 extension = self._browser.get_extension(ext_path) 313 for k in params_dict: 314 if getattr(self, params_dict[k]) is not '': 315 extension.ExecuteJavaScript('var %s = %s;' % 316 (k, getattr(self, params_dict[k]))) 317 318 # This opens a trap start page to capture tabs opened for first login. 319 # It will be closed when startTest is run. 320 extension.ExecuteJavaScript('chrome.windows.create(null, null);') 321 322 for i in range(self._loop_count): 323 start_time = time.time() 324 extension.ExecuteJavaScript('startTest();') 325 # the power test extension will report its status here 326 latch = self._testServer.add_wait_url('/status') 327 328 # this starts a thread in the server that listens to log 329 # information from the script 330 script_logging = self._testServer.add_wait_url(url='/log') 331 332 # dump any log entry that comes from the script into 333 # the debug log 334 self._testServer.add_url_handler(url='/log',\ 335 handler_func=(lambda handler, forms, loop_counter=i:\ 336 _extension_log_handler(handler, forms, loop_counter))) 337 338 pagetime_tracking = self._testServer.add_wait_url(url='/pagetime') 339 340 self._testServer.add_url_handler(url='/pagetime',\ 341 handler_func=(lambda handler, forms, test_instance=self, 342 loop_counter=i:\ 343 _extension_page_time_info_handler(handler, forms, 344 loop_counter, 345 test_instance))) 346 347 # setup a handler to simulate waking up the base of a detachable 348 # on user interaction. On scrolling, wake for 1s, on page 349 # navigation, wake for 10s. 350 self._testServer.add_url(url='/pagenav') 351 self._testServer.add_url(url='/scroll') 352 353 self._testServer.add_url_handler(url='/pagenav', 354 handler_func=(lambda handler, args, plt=self: 355 plt._detachable_handler.wake_base(10000))) 356 357 self._testServer.add_url_handler(url='/scroll', 358 handler_func=(lambda handler, args, plt=self: 359 plt._detachable_handler.wake_base(1000))) 360 # reset backlight level since powerd might've modified it 361 # based on ambient light 362 self._set_backlight_level() 363 self._set_lightbar_level() 364 if self._keyboard_backlight: 365 self._set_keyboard_backlight_level() 366 audio_helper.set_volume_levels(self._volume_level, 367 self._mic_gain) 368 369 low_battery = self._do_wait(self._verbose, self._loop_time, 370 latch) 371 372 script_logging.set() 373 pagetime_tracking.set() 374 375 self._log_loop_checkpoint(i, start_time, time.time()) 376 377 if self._verbose: 378 logging.debug('loop %d completed', i) 379 380 if low_battery: 381 logging.info('Exiting due to low battery') 382 break 383 384 # done with logging from the script, so we can collect that thread 385 t1 = time.time() 386 psr.refresh() 387 self._tmp_keyvals['minutes_battery_life_tested'] = (t1 - t0) / 60 388 self._tmp_keyvals.update(psr.get_keyvals()) 389 390 def postprocess_iteration(self): 391 """Postprocess: write keyvals / log and send data to power dashboard.""" 392 def _log_stats(prefix, stats): 393 if not len(stats): 394 return 395 np = numpy.array(stats) 396 logging.debug("%s samples: %d", prefix, len(np)) 397 logging.debug("%s mean: %.2f", prefix, np.mean()) 398 logging.debug("%s stdev: %.2f", prefix, np.std()) 399 logging.debug("%s max: %.2f", prefix, np.max()) 400 logging.debug("%s min: %.2f", prefix, np.min()) 401 402 403 def _log_per_loop_stats(): 404 samples_per_loop = self._loop_time / self._wait_time + 1 405 for kname in self._stats: 406 start_idx = 0 407 loop = 1 408 for end_idx in xrange(samples_per_loop, len(self._stats[kname]), 409 samples_per_loop): 410 _log_stats("%s loop %d" % (kname, loop), 411 self._stats[kname][start_idx:end_idx]) 412 loop += 1 413 start_idx = end_idx 414 415 416 def _log_all_stats(): 417 for kname in self._stats: 418 _log_stats(kname, self._stats[kname]) 419 420 421 for task, tstart, tend in self._task_tracker: 422 self._checkpoint_logger.checkpoint('_' + task, tstart, tend) 423 424 keyvals = {} 425 for log in self._meas_logs: 426 keyvals.update(log.calc()) 427 keyvals.update(self._statomatic.publish()) 428 429 if self._log_mem_bandwidth: 430 self._mlog.stop() 431 self._mlog.join() 432 433 _log_all_stats() 434 _log_per_loop_stats() 435 436 # record battery stats 437 if self._power_status.battery: 438 keyvals['a_current_now'] = self._power_status.battery[0].current_now 439 keyvals['ah_charge_full'] = \ 440 self._power_status.battery[0].charge_full 441 keyvals['ah_charge_full_design'] = \ 442 self._power_status.battery[0].charge_full_design 443 keyvals['ah_charge_start'] = self._ah_charge_start 444 keyvals['ah_charge_now'] = self._power_status.battery[0].charge_now 445 keyvals['ah_charge_used'] = keyvals['ah_charge_start'] - \ 446 keyvals['ah_charge_now'] 447 keyvals['wh_energy_start'] = self._wh_energy_start 448 keyvals['wh_energy_now'] = self._power_status.battery[0].energy 449 keyvals['wh_energy_used'] = keyvals['wh_energy_start'] - \ 450 keyvals['wh_energy_now'] 451 keyvals['v_voltage_min_design'] = \ 452 self._power_status.battery[0].voltage_min_design 453 keyvals['wh_energy_full_design'] = \ 454 self._power_status.battery[0].energy_full_design 455 keyvals['v_voltage_now'] = self._power_status.battery[0].voltage_now 456 457 keyvals.update(self._tmp_keyvals) 458 459 keyvals['percent_sys_low_battery'] = self._sys_low_batt_p 460 keyvals['seconds_sys_low_battery'] = self._sys_low_batt_s 461 voltage_np = numpy.array(self._stats['v_voltage_now']) 462 voltage_mean = voltage_np.mean() 463 keyvals['v_voltage_mean'] = voltage_mean 464 465 keyvals['wh_energy_powerlogger'] = \ 466 self._energy_use_from_powerlogger(keyvals) 467 468 if not self._power_status.on_ac() and keyvals['ah_charge_used'] > 0: 469 # For full runs, we should use charge to scale for battery life, 470 # since the voltage swing is accounted for. 471 # For short runs, energy will be a better estimate. 472 if self._loop_count > 1: 473 estimated_reps = (keyvals['ah_charge_full_design'] / 474 keyvals['ah_charge_used']) 475 else: 476 estimated_reps = (keyvals['wh_energy_full_design'] / 477 keyvals['wh_energy_powerlogger']) 478 479 bat_life_scale = estimated_reps * \ 480 ((100 - keyvals['percent_sys_low_battery']) / 100) 481 482 keyvals['minutes_battery_life'] = bat_life_scale * \ 483 keyvals['minutes_battery_life_tested'] 484 # In the case where sys_low_batt_s is non-zero subtract those 485 # minutes from the final extrapolation. 486 if self._sys_low_batt_s: 487 keyvals['minutes_battery_life'] -= self._sys_low_batt_s / 60 488 489 keyvals['a_current_rate'] = keyvals['ah_charge_used'] * 60 / \ 490 keyvals['minutes_battery_life_tested'] 491 keyvals['w_energy_rate'] = keyvals['wh_energy_used'] * 60 / \ 492 keyvals['minutes_battery_life_tested'] 493 if self._gaia_login: 494 self.output_perf_value(description='minutes_battery_life', 495 value=keyvals['minutes_battery_life'], 496 units='minutes', 497 higher_is_better=True) 498 499 minutes_battery_life_tested = keyvals['minutes_battery_life_tested'] 500 501 # TODO(coconutruben): overwrite write_perf_keyvals for all power 502 # tests and replace this once power_LoadTest inherits from power_Test. 503 # Dump all keyvals into debug keyvals. 504 _utils.write_keyval(os.path.join(self.resultsdir, 'debug_keyval'), 505 keyvals) 506 # Avoid polluting the keyvals with non-core domains. 507 core_keyvals = power_utils.get_core_keyvals(keyvals) 508 if not self._gaia_login: 509 core_keyvals = {'INVALID_%s' % str(k): v for k, v in 510 core_keyvals.iteritems()} 511 else: 512 for key, value in core_keyvals.iteritems(): 513 if key.startswith('percent_cpuidle') and \ 514 key.endswith('C0_time'): 515 self.output_perf_value(description=key, 516 value=value, 517 units='percent', 518 higher_is_better=False) 519 520 self.write_perf_keyval(core_keyvals) 521 for log in self._meas_logs: 522 log.save_results(self.resultsdir) 523 self._checkpoint_logger.save_checkpoint_data(self.resultsdir) 524 525 if minutes_battery_life_tested * 60 < self._loop_time : 526 logging.info('Data is less than 1 loop, skip sending to dashboard.') 527 return 528 pdash = power_dashboard.PowerLoggerDashboard( \ 529 self._plog, self.tagged_testname, self.resultsdir, 530 note=self._pdash_note) 531 pdash.upload() 532 cdash = power_dashboard.CPUStatsLoggerDashboard( \ 533 self._clog, self.tagged_testname, self.resultsdir, 534 note=self._pdash_note) 535 cdash.upload() 536 tdash = power_dashboard.TempLoggerDashboard( \ 537 self._tlog, self.tagged_testname, self.resultsdir, 538 note=self._pdash_note) 539 tdash.upload() 540 541 542 def cleanup(self): 543 if self._force_discharge: 544 power_utils.charge_control_by_ectool(True) 545 if self._backlight: 546 self._backlight.restore() 547 if self._services: 548 self._services.restore_services() 549 self._detachable_handler.restore() 550 551 # cleanup backchannel interface 552 # Prevent wifi congestion in test lab by forcing machines to forget the 553 # wifi AP we connected to at the start of the test. 554 if self._shill_proxy: 555 self._shill_proxy.remove_all_wifi_entries() 556 if self._backchannel: 557 self._backchannel.teardown() 558 if self._browser: 559 self._browser.close() 560 if self._testServer: 561 self._testServer.stop() 562 563 564 def _do_wait(self, verbose, seconds, latch): 565 latched = False 566 low_battery = False 567 total_time = seconds + self._wait_time 568 elapsed_time = 0 569 570 while elapsed_time < total_time: 571 time.sleep(self._wait_time) 572 elapsed_time += self._wait_time 573 574 self._power_status.refresh() 575 576 if not self._ac_ok and self._power_status.on_ac(): 577 raise error.TestError('Running on AC power now.') 578 579 if self._power_status.battery: 580 charge_now = self._power_status.battery[0].charge_now 581 energy_rate = self._power_status.battery[0].energy_rate 582 voltage_now = self._power_status.battery[0].voltage_now 583 self._stats['w_energy_rate'].append(energy_rate) 584 self._stats['v_voltage_now'].append(voltage_now) 585 if verbose: 586 logging.debug('ah_charge_now %f', charge_now) 587 logging.debug('w_energy_rate %f', energy_rate) 588 logging.debug('v_voltage_now %f', voltage_now) 589 590 low_battery = (self._power_status.percent_current_charge() < 591 self._test_low_batt_p) 592 593 latched = latch.is_set() 594 595 if latched or low_battery: 596 break 597 598 if latched: 599 # record chrome power extension stats 600 form_data = self._testServer.get_form_entries() 601 logging.debug(form_data) 602 for e in form_data: 603 key = 'ext_' + e 604 if key in self._tmp_keyvals: 605 self._tmp_keyvals[key] += "_%s" % form_data[e] 606 else: 607 self._tmp_keyvals[key] = form_data[e] 608 else: 609 logging.debug("Didn't get status back from power extension") 610 611 return low_battery 612 613 def _set_backlight_level(self): 614 self._backlight.set_default() 615 # record brightness level 616 self._tmp_keyvals['level_backlight_current'] = \ 617 self._backlight.get_level() 618 619 620 def _set_lightbar_level(self, level='off'): 621 """Set lightbar level. 622 623 Args: 624 level: string value to set lightbar to. See ectool for more details. 625 """ 626 rv = utils.system('which ectool', ignore_status=True) 627 if rv: 628 return 629 rv = utils.system('ectool lightbar %s' % level, ignore_status=True) 630 if rv: 631 logging.info('Assuming no lightbar due to non-zero exit status') 632 else: 633 logging.info('Setting lightbar to %s', level) 634 self._tmp_keyvals['level_lightbar_current'] = level 635 636 637 def _has_light_sensor(self): 638 """ 639 Determine if there is a light sensor on the board. 640 641 @returns True if this host has a light sensor or 642 False if it does not. 643 """ 644 # If the command exits with a failure status, 645 # we do not have a light sensor 646 cmd = 'check_powerd_config --ambient_light_sensor' 647 result = utils.run(cmd, ignore_status=True) 648 if result.exit_status: 649 logging.debug('Ambient light sensor not present') 650 return False 651 logging.debug('Ambient light sensor present') 652 return True 653 654 655 def _energy_use_from_powerlogger(self, keyval): 656 """ 657 Calculates the energy use, in Wh, used over the course of the run as 658 reported by the PowerLogger. 659 660 Args: 661 keyval: the dictionary of keyvals containing PowerLogger output 662 663 Returns: 664 energy_wh: total energy used over the course of this run 665 666 """ 667 energy_wh = 0 668 loop = 0 669 while True: 670 duration_key = 'loop%d_system_duration' % loop 671 avg_power_key = 'loop%d_system_pwr_avg' % loop 672 if duration_key not in keyval or avg_power_key not in keyval: 673 break 674 energy_wh += keyval[duration_key] * keyval[avg_power_key] / 3600 675 loop += 1 676 return energy_wh 677 678 679 def _has_hover_detection(self): 680 """ 681 Checks if hover is detected by the device. 682 683 Returns: 684 Returns True if the hover detection support is enabled. 685 Else returns false. 686 """ 687 688 cmd = 'check_powerd_config --hover_detection' 689 result = utils.run(cmd, ignore_status=True) 690 if result.exit_status: 691 logging.debug('Hover not present') 692 return False 693 logging.debug('Hover present') 694 return True 695 696 697 def _set_keyboard_backlight_level(self): 698 """ 699 Sets keyboard backlight based on light sensor and hover. 700 These values are based on UMA as mentioned in 701 https://bugs.chromium.org/p/chromium/issues/detail?id=603233#c10 702 703 ALS | hover | keyboard backlight level 704 --------------------------------------- 705 No | No | default 706 --------------------------------------- 707 Yes | No | 40% of default 708 -------------------------------------- 709 No | Yes | System with this configuration does not exist 710 -------------------------------------- 711 Yes | Yes | 30% of default 712 -------------------------------------- 713 714 Here default is no Ambient Light Sensor, no hover, 715 default always-on brightness level. 716 """ 717 718 default_level = self._keyboard_backlight.get_default_level() 719 level_to_set = default_level 720 has_light_sensor = self._has_light_sensor() 721 has_hover = self._has_hover_detection() 722 # TODO(ravisadineni):if (crbug: 603233) becomes default 723 # change this to reflect it. 724 if has_light_sensor and has_hover: 725 level_to_set = (30 * default_level) / 100 726 elif has_light_sensor: 727 level_to_set = (40 * default_level) / 100 728 elif has_hover: 729 logging.warn('Device has hover but no light sensor') 730 731 logging.info('Setting keyboard backlight to %d', level_to_set) 732 self._keyboard_backlight.set_level(level_to_set) 733 self._tmp_keyvals['percent_kbd_backlight'] = \ 734 self._keyboard_backlight.get_percent() 735 736 def _log_loop_checkpoint(self, loop, start, end): 737 loop_str = 'loop%d' % loop 738 self._checkpoint_logger.checkpoint(loop_str, start, end) 739 740 # Don't log section if we run custom tasks. 741 if self._tasks != '': 742 return 743 744 sections = [ 745 ('browsing', (0, 0.6)), 746 ('email', (0.6, 0.8)), 747 ('document', (0.8, 0.9)), 748 ('video', (0.9, 1)), 749 ] 750 751 # Use start time from extension if found by look for google.com start. 752 goog_str = loop_str+ '_web_page_www.google.com' 753 for item, start_extension, _ in self._task_tracker: 754 if item == goog_str: 755 if start_extension >= start: 756 start = start_extension 757 break 758 logging.warn('Timestamp from extension (%.2f) is earlier than' 759 'timestamp from autotest (%.2f).', 760 start_extension, start) 761 762 # Use default loop duration for incomplete loop. 763 duration = max(end - start, self._loop_time) 764 765 for section, fractions in sections: 766 s_start, s_end = (start + duration * fraction 767 for fraction in fractions) 768 if s_start > end: 769 break 770 if s_end > end: 771 s_end = end 772 self._checkpoint_logger.checkpoint(section, s_start, s_end) 773 loop_section = '_' + loop_str + '_' + section 774 self._checkpoint_logger.checkpoint(loop_section, s_start, s_end) 775 776 777 def _extension_log_handler(handler, form, loop_number): 778 """ 779 We use the httpd library to allow us to log whatever we 780 want from the extension JS script into the log files. 781 782 This method is provided to the server as a handler for 783 all requests that come for the log url in the testServer 784 785 unused parameter, because httpd passes the server itself 786 into the handler. 787 """ 788 if form: 789 for field in form.keys(): 790 logging.debug("[extension] @ loop_%d %s", loop_number, 791 form[field].value) 792 # we don't want to add url information to our keyvals. 793 # httpd adds them automatically so we remove them again 794 del handler.server._form_entries[field] 795 796 def _extension_page_time_info_handler(handler, form, loop_number, 797 test_instance): 798 page_timestamps = [] 799 800 stats_ids = ['mean', 'min', 'max', 'std'] 801 loadtime_measurements = [] 802 sorted_pagelt = [] 803 #show up to this number of slow page-loads 804 num_slow_page_loads = 5 805 806 if not form: 807 logging.debug("no page time information returned") 808 return 809 810 for field in form.keys(): 811 url = field[str.find(field, "http"):] # remove unique url salt 812 page = json.loads(form[field].value) 813 814 logging.debug("[extension] @ loop_%d url: %s start_time: %d", 815 loop_number, url, page['start_time']) 816 817 if page['end_load_time']: 818 logging.debug("[extension] @ loop_%d url: %s end_load_time: %d", 819 loop_number, url, page['end_load_time']) 820 821 load_time = page['end_load_time'] - page['start_time'] 822 823 loadtime_measurements.append(load_time) 824 sorted_pagelt.append((url, load_time)) 825 826 logging.debug("[extension] @ loop_%d url: %s load time: %d ms", 827 loop_number, url, load_time) 828 829 logging.debug("[extension] @ loop_%d url: %s end_browse_time: %d", 830 loop_number, url, page['end_browse_time']) 831 832 page_timestamps.append(page) 833 834 # we don't want to add url information to our keyvals. 835 # httpd adds them automatically so we remove them again 836 del handler.server._form_entries[field] 837 838 page_base = 'loop%d_web_page_' % loop_number 839 for page in page_timestamps: 840 page_failed = "_failed" 841 # timestamps from javascript are in milliseconds, change to seconds 842 scale = 1.0/1000 843 if page['end_load_time']: 844 tagname = page_base + page['url'] + "_load" 845 test_instance._task_tracker.append((tagname, 846 page['start_time'] * scale, page['end_load_time'] * scale)) 847 848 tagname = page_base + page['url'] + "_browse" 849 test_instance._task_tracker.append((tagname, 850 page['end_load_time'] * scale, page['end_browse_time'] * scale)) 851 852 page_failed = "" 853 854 tagname = page_base + page['url'] + page_failed 855 test_instance._task_tracker.append((tagname, 856 page['start_time'] * scale, page['end_browse_time'] * scale)) 857 858 loadtime_measurements = numpy.array(loadtime_measurements) 859 stats_vals = [loadtime_measurements.mean(), loadtime_measurements.min(), 860 loadtime_measurements.max(),loadtime_measurements.std()] 861 862 key_base = 'ext_ms_page_load_time_' 863 for i in range(len(stats_ids)): 864 key = key_base + stats_ids[i] 865 if key in test_instance._tmp_keyvals: 866 test_instance._tmp_keyvals[key] += "_%.2f" % stats_vals[i] 867 else: 868 test_instance._tmp_keyvals[key] = "%.2f" % stats_vals[i] 869 870 871 sorted_pagelt.sort(key=lambda item: item[1], reverse=True) 872 873 message = "The %d slowest page-load-times are:\n" % (num_slow_page_loads) 874 for url, msecs in sorted_pagelt[:num_slow_page_loads]: 875 message += "\t%s w/ %d ms" % (url, msecs) 876 877 logging.debug("%s\n", message) 878