1# Lint as: python2, python3 2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import logging, math, time 7 8from autotest_lib.client.bin import test 9from autotest_lib.client.common_lib import error 10from autotest_lib.client.cros import rtc 11from autotest_lib.client.cros.power import force_discharge_utils 12from autotest_lib.client.cros.power import power_dashboard 13from autotest_lib.client.cros.power import power_status 14from autotest_lib.client.cros.power import power_suspend 15from autotest_lib.client.cros.power import power_telemetry_utils 16from autotest_lib.client.cros.power import power_utils 17 18 19class power_Standby(test.test): 20 """Measure Standby power test.""" 21 version = 1 22 _percent_min_charge = 10 23 _min_sample_hours = 0.1 24 25 def initialize(self, pdash_note=''): 26 """Initialize.""" 27 self._pdash_note = pdash_note 28 self._checkpoint_logger = power_status.CheckpointLogger() 29 30 def run_once(self, 31 test_hours=None, 32 sample_hours=None, 33 max_milliwatts_standby=500, 34 ac_ok=False, 35 force_discharge='false', 36 suspend_state='', 37 bypass_check=False): 38 """Put DUT to suspend state for |sample_hours| and measure power.""" 39 if not power_utils.has_battery(): 40 raise error.TestNAError('Skipping test because DUT has no battery.') 41 42 if test_hours < sample_hours: 43 raise error.TestFail('Test hours must be greater than sample ' 44 'hours.') 45 46 # If we're measuring < 6min of standby then the S0 time is not 47 # negligible. Note, reasonable rule of thumb is S0 idle is ~10-20 times 48 # standby power. 49 if sample_hours < self._min_sample_hours and not bypass_check: 50 raise error.TestFail('Must standby more than %.2f hours.' % \ 51 self._min_sample_hours) 52 53 power_stats = power_status.get_status() 54 55 self._force_discharge_success = force_discharge_utils.process( 56 force_discharge, power_stats) 57 if self._force_discharge_success: 58 ac_ok = True 59 60 if force_discharge == 'false' and not ac_ok and power_stats.on_ac(): 61 raise error.TestError('On AC, please unplug power supply.') 62 63 charge_start = power_stats.battery.charge_now 64 voltage_start = power_stats.battery.voltage_now 65 66 max_hours = ((charge_start * voltage_start) / 67 (max_milliwatts_standby / 1000.)) 68 if max_hours < test_hours: 69 raise error.TestFail('Battery not charged adequately for test.') 70 71 suspender = power_suspend.Suspender(self.resultsdir, 72 suspend_state=suspend_state) 73 74 elapsed_hours = 0 75 76 results = {} 77 loop = 0 78 start_ts = time.time() 79 80 while elapsed_hours < test_hours: 81 charge_before = power_stats.battery.charge_now 82 before_suspend_secs = rtc.get_seconds() 83 suspender.suspend(duration=sample_hours * 3600) 84 after_suspend_secs = rtc.get_seconds() 85 86 power_stats.refresh() 87 if power_stats.percent_current_charge() < self._percent_min_charge: 88 logging.warning('Battery = %.2f%%. Too low to continue.') 89 break 90 91 # check that the RTC slept the correct amount of time as there could 92 # potentially be another wake source that would spoil the test. 93 actual_hours = (after_suspend_secs - before_suspend_secs) / 3600.0 94 percent_diff = math.fabs((actual_hours - sample_hours) / ( 95 (actual_hours + sample_hours) / 2) * 100) 96 if percent_diff > 2 and not bypass_check: 97 err = 'Requested standby time and actual varied by %.2f%%.' \ 98 % percent_diff 99 raise error.TestFail(err) 100 101 # Check resulting charge consumption 102 charge_used = charge_before - power_stats.battery.charge_now 103 elapsed_hours += actual_hours 104 logging.debug( 105 'loop%d done: loop hours %.3f, elapsed hours %.3f ' 106 'charge used: %.3f', loop, actual_hours, elapsed_hours, 107 charge_used) 108 loop += 1 109 110 end_ts = time.time() 111 offset = (end_ts - start_ts - elapsed_hours * 3600) / 2. 112 offset += suspender.get_suspend_delay() 113 start_ts += offset 114 end_ts -= offset 115 power_telemetry_utils.start_measurement(start_ts) 116 power_telemetry_utils.end_measurement(end_ts) 117 self._checkpoint_logger.checkpoint(self.tagged_testname, 118 start_ts, end_ts) 119 charge_end = power_stats.battery.charge_now 120 total_charge_used = charge_start - charge_end 121 if total_charge_used <= 0: 122 if not bypass_check: 123 raise error.TestError('Charge used is suspect.') 124 # The standby time is too short, make it 0.001 to avoid divide by 0. 125 logging.warning('Total Charge used was 0') 126 total_charge_used = 0.001 127 128 voltage_end = power_stats.battery.voltage_now 129 standby_hours = power_stats.battery.charge_full_design / \ 130 total_charge_used * elapsed_hours 131 energy_used = (voltage_start + voltage_end) / 2 * \ 132 total_charge_used 133 134 results['ah_charge_start'] = charge_start 135 results['ah_charge_now'] = charge_end 136 results['ah_charge_used'] = total_charge_used 137 results['force_discharge'] = self._force_discharge_success 138 results['hours_standby_time'] = standby_hours 139 results['hours_standby_time_tested'] = elapsed_hours 140 results['v_voltage_start'] = voltage_start 141 results['v_voltage_now'] = voltage_end 142 results['w_energy_rate'] = energy_used / elapsed_hours 143 results['wh_energy_used'] = energy_used 144 145 self.write_perf_keyval(results) 146 logger = power_dashboard.KeyvalLogger(start_ts, end_ts) 147 logger.add_item('system', results['w_energy_rate'], 'watt', 'power') 148 pdash = power_dashboard.get_dashboard_factory().createDashboard(logger, 149 self.tagged_testname, self.resultsdir, note=self._pdash_note) 150 pdash.upload() 151 self._checkpoint_logger.save_checkpoint_data(self.resultsdir) 152 153 self.output_perf_value(description='hours_standby_time', 154 value=results['hours_standby_time'], 155 units='hours', higher_is_better=True) 156 self.output_perf_value(description='w_energy_rate', 157 value=results['w_energy_rate'], units='watts', 158 higher_is_better=False) 159 160 # need to sleep for some time to allow network connection to return 161 time.sleep(10) 162 163 def cleanup(self): 164 """Clean up force discharge.""" 165 force_discharge_utils.restore(self._force_discharge_success) 166