Home | History | Annotate | Download | only in power_Standby
      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 
      5 import logging, math, time
      6 
      7 from autotest_lib.client.bin import test
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.cros import rtc
     10 from autotest_lib.client.cros.power import power_dashboard
     11 from autotest_lib.client.cros.power import power_status
     12 from autotest_lib.client.cros.power import power_telemetry_utils
     13 from autotest_lib.client.cros.power import power_suspend
     14 from autotest_lib.client.cros.power import power_utils
     15 
     16 
     17 class 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