xref: /aosp_15_r20/external/autotest/client/site_tests/power_SuspendStress/power_SuspendStress.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright (c) 2013 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, numpy, random, time
7
8from autotest_lib.client.bin import test, utils
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.common_lib.cros.network import interface
11from autotest_lib.client.cros.networking import shill_proxy
12from autotest_lib.client.cros.power import power_suspend, sys_power
13
14class power_SuspendStress(test.test):
15    """Class for test."""
16    version = 1
17
18    def initialize(self, duration, idle=False, init_delay=0, min_suspend=0,
19                   min_resume=5, max_resume_window=3, check_connection=True,
20                   suspend_iterations=None, suspend_state='',
21                   modemfwd_workaround=False):
22        """
23        Entry point.
24
25        @param duration: total run time of the test
26        @param idle: use sys_power.idle_suspend method.
27                (use with stub_IdleSuspend)
28        @param init_delay: wait this many seconds before starting the test to
29                give parallel tests time to get started
30        @param min_suspend: suspend durations will be chosen randomly out of
31                the interval between min_suspend and min_suspend + 3 seconds.
32        @param min_resume: minimal time in seconds between suspends.
33        @param max_resume_window: maximum range to use between suspends. i.e.,
34                we will stay awake between min_resume and min_resume +
35                max_resume_window seconds.
36        @param check_connection: If true, we check that the network interface
37                used for testing is up after resume. Otherwsie we reboot.
38        @param suspend_iterations: number of times to attempt suspend.  If
39                !=None has precedence over duration.
40        @param suspend_state: Force to suspend to a specific
41                state ("mem" or "freeze"). If the string is empty, suspend
42                state is left to the default pref on the system.
43        @param modemfwd_workaround: disable the modemfwd daemon as a workaround
44                for its bad behavior during modem firmware update.
45        """
46        self._endtime = time.time()
47        if duration:
48            self._endtime += duration
49        self._init_delay = init_delay
50        self._min_suspend = min_suspend
51        self._min_resume = min_resume
52        self._max_resume_window = max_resume_window
53        self._check_connection = check_connection
54        self._suspend_iterations = suspend_iterations
55        self._suspend_state = suspend_state
56        self._modemfwd_workaround = modemfwd_workaround
57        self._method = sys_power.idle_suspend if idle else sys_power.suspend_for
58
59    def _done(self):
60        if self._suspend_iterations != None:
61            self._suspend_iterations -= 1
62            return self._suspend_iterations < 0
63        return time.time() >= self._endtime
64
65    def _get_default_network_interface(self):
66        iface = shill_proxy.ShillProxy().get_default_interface_name()
67        if not iface:
68            return None
69        return interface.Interface(iface)
70
71    def run_once(self):
72        time.sleep(self._init_delay)
73        self._suspender = power_suspend.Suspender(
74                self.resultsdir, method=self._method,
75                suspend_state=self._suspend_state)
76        # TODO(b/164255562) Temporary workaround for misbehaved modemfwd
77        if self._modemfwd_workaround:
78            utils.stop_service('modemfwd', ignore_status=True)
79        # Find the interface which is used for most communication.
80        # We assume the interface connects to the gateway and has the lowest
81        # metric.
82        if self._check_connection:
83            iface = utils.poll_for_condition(
84                        self._get_default_network_interface,
85                        desc='Find default network interface')
86            logging.info('Found default network interface: %s', iface.name)
87
88        while not self._done():
89            time.sleep(self._min_resume +
90                       random.randint(0, self._max_resume_window))
91            # Check the network interface to the caller is still available
92            if self._check_connection:
93                # Give a 10 second window for the network to come back.
94                try:
95                    utils.poll_for_condition(iface.is_link_operational,
96                                             desc='Link is operational')
97                except utils.TimeoutError:
98                    logging.error('Link to the server gone, reboot')
99                    utils.system('reboot')
100                    # Reboot may return; raise a TestFail() to abort too, even
101                    # though the server likely won't see this.
102                    raise error.TestFail('Link is gone; rebooting')
103
104            self._suspender.suspend(random.randint(0, 3) + self._min_suspend)
105
106
107    def postprocess_iteration(self):
108        if self._suspender.successes:
109            keyvals = {'suspend_iterations': len(self._suspender.successes)}
110            for key in self._suspender.successes[0]:
111                values = [result[key] for result in self._suspender.successes]
112                keyvals[key + '_mean'] = numpy.mean(values)
113                keyvals[key + '_stddev'] = numpy.std(values)
114                keyvals[key + '_min'] = numpy.amin(values)
115                keyvals[key + '_max'] = numpy.amax(values)
116            self.write_perf_keyval(keyvals)
117        if self._suspender.failures:
118            total = len(self._suspender.failures)
119            iterations = len(self._suspender.successes) + total
120            timeout = kernel = firmware = spurious = 0
121            for failure in self._suspender.failures:
122                if type(failure) is sys_power.SuspendTimeout: timeout += 1
123                if type(failure) is sys_power.KernelError: kernel += 1
124                if type(failure) is sys_power.FirmwareError: firmware += 1
125                if type(failure) is sys_power.SpuriousWakeupError: spurious += 1
126            if total == kernel + timeout:
127                raise error.TestWarn('%d non-fatal suspend failures in %d '
128                        'iterations (%d timeouts, %d kernel warnings)' %
129                        (total, iterations, timeout, kernel))
130            if total == 1:
131                # just throw it as is, makes aggregation on dashboards easier
132                raise self._suspender.failures[0]
133            raise error.TestFail('%d suspend failures in %d iterations (%d '
134                    'timeouts, %d kernel warnings, %d firmware errors, %d '
135                    'spurious wakeups)' %
136                    (total, iterations, timeout, kernel, firmware, spurious))
137
138
139    def cleanup(self):
140        """
141        Clean this up before we wait ages for all the log copying to finish...
142        """
143        self._suspender.finalize()
144        if self._modemfwd_workaround:
145            utils.start_service('modemfwd', ignore_status=True)
146