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