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