1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright 2018 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Liimport logging 6*9c5db199SXin Liimport re 7*9c5db199SXin Liimport time 8*9c5db199SXin Li 9*9c5db199SXin Lifrom autotest_lib.client.bin import test 10*9c5db199SXin Lifrom autotest_lib.client.bin import utils 11*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 12*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import arc_common 13*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import retry 14*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros.network import interface 15*9c5db199SXin Lifrom autotest_lib.client.cros import service_stopper 16*9c5db199SXin Lifrom autotest_lib.client.cros.camera import camera_utils 17*9c5db199SXin Lifrom autotest_lib.client.cros.power import force_discharge_utils 18*9c5db199SXin Lifrom autotest_lib.client.cros.power import power_dashboard 19*9c5db199SXin Lifrom autotest_lib.client.cros.power import power_status 20*9c5db199SXin Lifrom autotest_lib.client.cros.power import power_telemetry_utils 21*9c5db199SXin Lifrom autotest_lib.client.cros.power import power_utils 22*9c5db199SXin Lifrom autotest_lib.client.cros.video import histogram_verifier 23*9c5db199SXin Li 24*9c5db199SXin Liclass power_Test(test.test): 25*9c5db199SXin Li """Optional base class power related tests.""" 26*9c5db199SXin Li version = 1 27*9c5db199SXin Li 28*9c5db199SXin Li keypress_histogram = 'Event.Latency.EndToEnd.KeyPress' 29*9c5db199SXin Li histogram_re = 'Histogram: %s recorded (\d+) samples, mean = (\d+\.\d+)' 30*9c5db199SXin Li hist_percentile_re = '^(\d+).+\{(\d+)\.\d+\%\}' 31*9c5db199SXin Li 32*9c5db199SXin Li def initialize(self, 33*9c5db199SXin Li seconds_period=20., 34*9c5db199SXin Li pdash_note='', 35*9c5db199SXin Li force_discharge='false', 36*9c5db199SXin Li check_network=False, 37*9c5db199SXin Li run_arc=True): 38*9c5db199SXin Li """Perform necessary initialization prior to power test run. 39*9c5db199SXin Li 40*9c5db199SXin Li @param seconds_period: float of probing interval in seconds. 41*9c5db199SXin Li @param pdash_note: note of the current run to send to power dashboard. 42*9c5db199SXin Li @param force_discharge: string of whether to tell ec to discharge 43*9c5db199SXin Li battery even when the charger is plugged in. 'false' means no 44*9c5db199SXin Li forcing discharge; 'true' means forcing discharge and raising an 45*9c5db199SXin Li error when it fails; 'optional' means forcing discharge when 46*9c5db199SXin Li possible but not raising an error when it fails, which is more 47*9c5db199SXin Li friendly to devices without a battery. 48*9c5db199SXin Li @param check_network: check that Ethernet interface is not running. 49*9c5db199SXin Li @param run_arc: bool, whether to run with ARC (if available) 50*9c5db199SXin Li 51*9c5db199SXin Li @var backlight: power_utils.Backlight object. 52*9c5db199SXin Li @var keyvals: dictionary of result keyvals. 53*9c5db199SXin Li @var status: power_status.SysStat object. 54*9c5db199SXin Li 55*9c5db199SXin Li @var _checkpoint_logger: power_status.CheckpointLogger to track 56*9c5db199SXin Li checkpoint data. 57*9c5db199SXin Li @var _psr: power_utils.DisplayPanelSelfRefresh object to monitor PSR. 58*9c5db199SXin Li @var _services: service_stopper.ServiceStopper object. 59*9c5db199SXin Li @var _start_time: float of time in seconds since Epoch test started. 60*9c5db199SXin Li @var _stats: power_status.StatoMatic object. 61*9c5db199SXin Li @var _meas_logs: list of power_status.MeasurementLoggers 62*9c5db199SXin Li """ 63*9c5db199SXin Li super(power_Test, self).initialize() 64*9c5db199SXin Li self.keyvals = dict() 65*9c5db199SXin Li self.status = power_status.get_status() 66*9c5db199SXin Li 67*9c5db199SXin Li self._checkpoint_logger = power_status.CheckpointLogger() 68*9c5db199SXin Li self._seconds_period = seconds_period 69*9c5db199SXin Li 70*9c5db199SXin Li self._force_discharge_success = force_discharge_utils.process( 71*9c5db199SXin Li force_discharge, self.status) 72*9c5db199SXin Li self.backlight = power_utils.Backlight( 73*9c5db199SXin Li force_battery=self._force_discharge_success) 74*9c5db199SXin Li self.backlight.set_default() 75*9c5db199SXin Li 76*9c5db199SXin Li ifaces = [iface for iface in interface.get_interfaces() 77*9c5db199SXin Li if (not iface.is_wifi_device() and 78*9c5db199SXin Li iface.name.startswith('eth'))] 79*9c5db199SXin Li logging.debug('Ethernet interfaces include: %s', 80*9c5db199SXin Li str([iface.name for iface in ifaces])) 81*9c5db199SXin Li for iface in ifaces: 82*9c5db199SXin Li if check_network and iface.is_lower_up: 83*9c5db199SXin Li raise error.TestError('Ethernet interface is active. ' 84*9c5db199SXin Li 'Please remove Ethernet cable.') 85*9c5db199SXin Li 86*9c5db199SXin Li self._psr = power_utils.DisplayPanelSelfRefresh() 87*9c5db199SXin Li self._services = service_stopper.ServiceStopper( 88*9c5db199SXin Li service_stopper.ServiceStopper.POWER_DRAW_SERVICES) 89*9c5db199SXin Li self._services.stop_services() 90*9c5db199SXin Li self._stats = power_status.StatoMatic() 91*9c5db199SXin Li 92*9c5db199SXin Li self._meas_logs = power_status.create_measurement_loggers( 93*9c5db199SXin Li seconds_period, self._checkpoint_logger) 94*9c5db199SXin Li logging.debug('measurement loggers (%d):', len(self._meas_logs)) 95*9c5db199SXin Li for log in self._meas_logs: 96*9c5db199SXin Li logging.debug('%s: %s', type(log).__name__, ", ".join(log.domains)) 97*9c5db199SXin Li 98*9c5db199SXin Li self._pdash_note = pdash_note 99*9c5db199SXin Li self._failure_messages = [] 100*9c5db199SXin Li 101*9c5db199SXin Li self._arc_mode = arc_common.ARC_MODE_DISABLED 102*9c5db199SXin Li if run_arc and utils.is_arc_available(): 103*9c5db199SXin Li self._arc_mode = arc_common.ARC_MODE_ENABLED 104*9c5db199SXin Li self.keyvals['arc_mode'] = self._arc_mode 105*9c5db199SXin Li 106*9c5db199SXin Li def get_extra_browser_args_for_camera_test(self): 107*9c5db199SXin Li """Return Chrome args for camera power test.""" 108*9c5db199SXin Li ret = [ 109*9c5db199SXin Li # No pop up to ask permission to record video. 110*9c5db199SXin Li '--use-fake-ui-for-media-stream', 111*9c5db199SXin Li # Allow 2 windows side by side. 112*9c5db199SXin Li '--force-tablet-mode=clamshell', 113*9c5db199SXin Li # Prefer using constant frame rate for camera streaming. 114*9c5db199SXin Li '--enable-features=PreferConstantFrameRate', 115*9c5db199SXin Li # Bypass HID detection for Chromebox / Chromebase. 116*9c5db199SXin Li '--disable-hid-detection-on-oobe', 117*9c5db199SXin Li # Disable test account info sync, eg. Wi-Fi credentials, 118*9c5db199SXin Li # so that each test run does not remember info from last test 119*9c5db199SXin Li # run. 120*9c5db199SXin Li '--disable-sync' 121*9c5db199SXin Li ] 122*9c5db199SXin Li 123*9c5db199SXin Li # Use fake camera for DUT without camera, e.g. chromebox. 124*9c5db199SXin Li if not camera_utils.has_builtin_or_vivid_camera(): 125*9c5db199SXin Li ret.append('--use-fake-device-for-media-stream') 126*9c5db199SXin Li self.keyvals['use_fake_camera'] = 1 127*9c5db199SXin Li else: 128*9c5db199SXin Li self.keyvals['use_fake_camera'] = 0 129*9c5db199SXin Li return ret 130*9c5db199SXin Li 131*9c5db199SXin Li def warmup(self, warmup_time=30): 132*9c5db199SXin Li """Warm up. 133*9c5db199SXin Li 134*9c5db199SXin Li Wait between initialization and run_once for new settings to stabilize. 135*9c5db199SXin Li 136*9c5db199SXin Li @param warmup_time: integer of seconds to warmup. 137*9c5db199SXin Li """ 138*9c5db199SXin Li time.sleep(warmup_time) 139*9c5db199SXin Li 140*9c5db199SXin Li def start_measurements(self): 141*9c5db199SXin Li """Start measurements.""" 142*9c5db199SXin Li for log in self._meas_logs: 143*9c5db199SXin Li log.start() 144*9c5db199SXin Li self._start_time = time.time() 145*9c5db199SXin Li if self.status.battery: 146*9c5db199SXin Li self._start_energy = self.status.battery.energy 147*9c5db199SXin Li self._keyvallogger = power_dashboard.KeyvalLogger(self._start_time) 148*9c5db199SXin Li power_telemetry_utils.start_measurement() 149*9c5db199SXin Li 150*9c5db199SXin Li def loop_sleep(self, loop, sleep_secs): 151*9c5db199SXin Li """Jitter free sleep. 152*9c5db199SXin Li 153*9c5db199SXin Li @param loop: integer of loop (1st is zero). 154*9c5db199SXin Li @param sleep_secs: integer of desired sleep seconds. 155*9c5db199SXin Li """ 156*9c5db199SXin Li next_time = self._start_time + (loop + 1) * sleep_secs 157*9c5db199SXin Li time.sleep(next_time - time.time()) 158*9c5db199SXin Li 159*9c5db199SXin Li def checkpoint_measurements(self, name, start_time=None): 160*9c5db199SXin Li """Checkpoint measurements. 161*9c5db199SXin Li 162*9c5db199SXin Li @param name: string name of measurement being checkpointed. 163*9c5db199SXin Li @param start_time: float of time in seconds since Epoch that 164*9c5db199SXin Li measurements being checkpointed began. 165*9c5db199SXin Li """ 166*9c5db199SXin Li if not start_time: 167*9c5db199SXin Li start_time = self._start_time 168*9c5db199SXin Li self.status.refresh() 169*9c5db199SXin Li self._checkpoint_logger.checkpoint(name, start_time) 170*9c5db199SXin Li self._psr.refresh() 171*9c5db199SXin Li 172*9c5db199SXin Li @retry.retry(Exception, timeout_min=1, delay_sec=2) 173*9c5db199SXin Li def collect_keypress_latency(self, cr): 174*9c5db199SXin Li """Collect keypress latency information from Histograms. 175*9c5db199SXin Li 176*9c5db199SXin Li @param cr: object, the Chrome instance 177*9c5db199SXin Li """ 178*9c5db199SXin Li 179*9c5db199SXin Li keypress_histogram_end = histogram_verifier.get_histogram( 180*9c5db199SXin Li cr, self.keypress_histogram) 181*9c5db199SXin Li matches = re.search((self.histogram_re % self.keypress_histogram), 182*9c5db199SXin Li keypress_histogram_end) 183*9c5db199SXin Li 184*9c5db199SXin Li if matches: 185*9c5db199SXin Li count = int(matches.group(1)) 186*9c5db199SXin Li mean_latency = float(matches.group(2)) 187*9c5db199SXin Li logging.info('latency count %d mean %f', count, mean_latency) 188*9c5db199SXin Li self.keyvals['keypress_cnt'] = count 189*9c5db199SXin Li self.keyvals['keypress_latency_us_avg'] = mean_latency 190*9c5db199SXin Li self.output_perf_value(description='keypress_cnt', value=count, 191*9c5db199SXin Li higher_is_better=True) 192*9c5db199SXin Li self.output_perf_value(description='keypress_latency_us_avg', 193*9c5db199SXin Li value=mean_latency, 194*9c5db199SXin Li higher_is_better=False) 195*9c5db199SXin Li self._keyvallogger.add_item('keypress_cnt', count, 'point', 196*9c5db199SXin Li 'keypress') 197*9c5db199SXin Li self._keyvallogger.add_item('keypress_latency_us_avg', 198*9c5db199SXin Li mean_latency, 'point', 'keypress') 199*9c5db199SXin Li 200*9c5db199SXin Li # Capture the first bucket >= 90th percentile 201*9c5db199SXin Li for s in keypress_histogram_end.splitlines(): 202*9c5db199SXin Li matches = re.search((self.hist_percentile_re), s) 203*9c5db199SXin Li if matches: 204*9c5db199SXin Li lat = int(matches.group(1)) 205*9c5db199SXin Li perc = int(matches.group(2)) 206*9c5db199SXin Li if perc >= 90: 207*9c5db199SXin Li self.keyvals['keypress_latency_us_high'] = lat 208*9c5db199SXin Li self.keyvals['keypress_high_percentile'] = perc 209*9c5db199SXin Li self.output_perf_value( 210*9c5db199SXin Li description='keypress_latency_us_high', value=lat, 211*9c5db199SXin Li higher_is_better=False) 212*9c5db199SXin Li self.output_perf_value( 213*9c5db199SXin Li description='keypress_high_percentile', value=perc, 214*9c5db199SXin Li higher_is_better=False) 215*9c5db199SXin Li self._keyvallogger.add_item('keypress_latency_us_high', 216*9c5db199SXin Li lat, 'point', 'keypress') 217*9c5db199SXin Li self._keyvallogger.add_item('keypress_high_percentile', 218*9c5db199SXin Li perc, 'point', 'keypress') 219*9c5db199SXin Li break 220*9c5db199SXin Li 221*9c5db199SXin Li def publish_keyvals(self): 222*9c5db199SXin Li """Publish power result keyvals.""" 223*9c5db199SXin Li keyvals = self._stats.publish() 224*9c5db199SXin Li keyvals['level_backlight_max'] = self.backlight.get_max_level() 225*9c5db199SXin Li keyvals['level_backlight_current'] = self.backlight.get_level() 226*9c5db199SXin Li 227*9c5db199SXin Li # record battery stats if battery exists 228*9c5db199SXin Li keyvals['b_on_ac'] = int(not self._force_discharge_success 229*9c5db199SXin Li and self.status.on_ac()) 230*9c5db199SXin Li keyvals['force_discharge'] = int(self._force_discharge_success) 231*9c5db199SXin Li for key in [ 232*9c5db199SXin Li 'b_on_ac', 'force_discharge', 'percent_usb_suspended_time' 233*9c5db199SXin Li ]: 234*9c5db199SXin Li self._keyvallogger.add_item(key, keyvals[key], 'point', 'perf') 235*9c5db199SXin Li 236*9c5db199SXin Li if self.status.battery: 237*9c5db199SXin Li keyvals['ah_charge_full'] = self.status.battery.charge_full 238*9c5db199SXin Li keyvals['ah_charge_full_design'] = \ 239*9c5db199SXin Li self.status.battery.charge_full_design 240*9c5db199SXin Li keyvals['ah_charge_now'] = self.status.battery.charge_now 241*9c5db199SXin Li keyvals['a_current_now'] = self.status.battery.current_now 242*9c5db199SXin Li 243*9c5db199SXin Li keyvals['wh_energy_full'] = self.status.battery.energy_full 244*9c5db199SXin Li keyvals['wh_energy_full_design'] = \ 245*9c5db199SXin Li self.status.battery.energy_full_design 246*9c5db199SXin Li keyvals['wh_energy_now'] = self.status.battery.energy 247*9c5db199SXin Li keyvals['wh_energy_start'] = self._start_energy 248*9c5db199SXin Li energy_used = self._start_energy - self.status.battery.energy 249*9c5db199SXin Li runtime_minutes = (time.time() - self._start_time) / 60. 250*9c5db199SXin Li keyvals['wh_energy_used'] = energy_used 251*9c5db199SXin Li keyvals['minutes_tested'] = runtime_minutes 252*9c5db199SXin Li self._keyvallogger.add_item('minutes_tested', 253*9c5db199SXin Li keyvals['minutes_tested'], 'point', 254*9c5db199SXin Li 'perf') 255*9c5db199SXin Li 256*9c5db199SXin Li low_batt = power_utils.get_low_battery_shutdown_percent() 257*9c5db199SXin Li keyvals['percent_sys_low_battery'] = low_batt 258*9c5db199SXin Li 259*9c5db199SXin Li if energy_used > 0 and runtime_minutes > 1: 260*9c5db199SXin Li keyvals['w_energy_rate'] = energy_used * 60. / runtime_minutes 261*9c5db199SXin Li self._keyvallogger.add_item('w_energy_rate', 262*9c5db199SXin Li keyvals['w_energy_rate'], 'point', 263*9c5db199SXin Li 'perf') 264*9c5db199SXin Li energy_avail = self.status.battery.energy_full_design * \ 265*9c5db199SXin Li ((100. - low_batt) / 100.) 266*9c5db199SXin Li keyvals['minutes_battery_life'] = energy_avail / energy_used * \ 267*9c5db199SXin Li runtime_minutes 268*9c5db199SXin Li self._keyvallogger.add_item('minutes_battery_life', 269*9c5db199SXin Li keyvals['minutes_battery_life'], 270*9c5db199SXin Li 'point', 'perf') 271*9c5db199SXin Li keyvals['hours_battery_life'] = \ 272*9c5db199SXin Li keyvals['minutes_battery_life'] / 60. 273*9c5db199SXin Li self._keyvallogger.add_item('hours_battery_life', 274*9c5db199SXin Li keyvals['hours_battery_life'], 275*9c5db199SXin Li 'point', 'perf') 276*9c5db199SXin Li 277*9c5db199SXin Li keyvals['v_voltage_min_design'] = \ 278*9c5db199SXin Li self.status.battery.voltage_min_design 279*9c5db199SXin Li keyvals['v_voltage_now'] = self.status.battery.voltage_now 280*9c5db199SXin Li 281*9c5db199SXin Li for log in self._meas_logs: 282*9c5db199SXin Li keyvals.update(log.calc()) 283*9c5db199SXin Li keyvals.update(self._psr.get_keyvals()) 284*9c5db199SXin Li 285*9c5db199SXin Li self.keyvals.update(keyvals) 286*9c5db199SXin Li 287*9c5db199SXin Li core_keyvals = power_utils.get_core_keyvals(self.keyvals) 288*9c5db199SXin Li self.write_perf_keyval(core_keyvals) 289*9c5db199SXin Li 290*9c5db199SXin Li def publish_dashboard(self): 291*9c5db199SXin Li """Report results to chromeperf & power dashboard.""" 292*9c5db199SXin Li 293*9c5db199SXin Li self.publish_keyvals() 294*9c5db199SXin Li 295*9c5db199SXin Li # publish power values 296*9c5db199SXin Li for key, values in self.keyvals.items(): 297*9c5db199SXin Li if key.endswith('pwr_avg'): 298*9c5db199SXin Li self.output_perf_value(description=key, value=values, units='W', 299*9c5db199SXin Li higher_is_better=False, graph='power') 300*9c5db199SXin Li 301*9c5db199SXin Li # publish temperature values 302*9c5db199SXin Li for key, values in self.keyvals.items(): 303*9c5db199SXin Li if key.endswith('temp_avg'): 304*9c5db199SXin Li self.output_perf_value(description=key, value=values, units='C', 305*9c5db199SXin Li higher_is_better=False, graph='temperature') 306*9c5db199SXin Li 307*9c5db199SXin Li # publish fps values 308*9c5db199SXin Li for key, values in self.keyvals.items(): 309*9c5db199SXin Li if key.endswith('fps_avg'): 310*9c5db199SXin Li self.output_perf_value(description=key, value=values, 311*9c5db199SXin Li units='fps', higher_is_better=True, graph='fps') 312*9c5db199SXin Li 313*9c5db199SXin Li # include KeyvalLogger in dashboard 314*9c5db199SXin Li self._meas_logs.append(self._keyvallogger) 315*9c5db199SXin Li 316*9c5db199SXin Li # publish to power dashboard 317*9c5db199SXin Li dashboard_factory = power_dashboard.get_dashboard_factory() 318*9c5db199SXin Li for log in self._meas_logs: 319*9c5db199SXin Li dashboard = dashboard_factory.createDashboard(log, 320*9c5db199SXin Li self.tagged_testname, self.resultsdir, note=self._pdash_note) 321*9c5db199SXin Li dashboard.upload() 322*9c5db199SXin Li 323*9c5db199SXin Li def _save_results(self): 324*9c5db199SXin Li """Save results of each logger in resultsdir.""" 325*9c5db199SXin Li for log in self._meas_logs: 326*9c5db199SXin Li log.save_results(self.resultsdir) 327*9c5db199SXin Li self._checkpoint_logger.save_checkpoint_data(self.resultsdir) 328*9c5db199SXin Li 329*9c5db199SXin Li def postprocess_iteration(self): 330*9c5db199SXin Li """Write keyval and send data to dashboard.""" 331*9c5db199SXin Li power_telemetry_utils.end_measurement() 332*9c5db199SXin Li self.status.refresh() 333*9c5db199SXin Li for log in self._meas_logs: 334*9c5db199SXin Li log.done = True 335*9c5db199SXin Li super(power_Test, self).postprocess_iteration() 336*9c5db199SXin Li self.publish_dashboard() 337*9c5db199SXin Li self._save_results() 338*9c5db199SXin Li power_dashboard.generate_parallax_report(self.outputdir) 339*9c5db199SXin Li if self._failure_messages: 340*9c5db199SXin Li raise error.TestFail('Test has failed with messages: %s' % 341*9c5db199SXin Li self._failure_messages) 342*9c5db199SXin Li 343*9c5db199SXin Li def cleanup(self): 344*9c5db199SXin Li """Reverse setting change in initialization.""" 345*9c5db199SXin Li force_discharge_utils.restore(self._force_discharge_success) 346*9c5db199SXin Li if self.backlight: 347*9c5db199SXin Li self.backlight.restore() 348*9c5db199SXin Li self._services.restore_services() 349*9c5db199SXin Li super(power_Test, self).cleanup() 350