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