xref: /aosp_15_r20/external/autotest/client/site_tests/power_Standby/power_Standby.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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