145 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			145 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
 | |
| # Use of this source code is governed by a BSD-style license that can be
 | |
| # found in the LICENSE file.
 | |
| 
 | |
| import logging, numpy, random, time
 | |
| 
 | |
| from autotest_lib.client.bin import test, utils
 | |
| from autotest_lib.client.common_lib import error
 | |
| from autotest_lib.client.common_lib.cros.network import interface
 | |
| from autotest_lib.client.cros.networking import shill_proxy
 | |
| from autotest_lib.client.cros.power import power_suspend, sys_power
 | |
| 
 | |
| class power_SuspendStress(test.test):
 | |
|     """Class for test."""
 | |
|     version = 1
 | |
| 
 | |
|     def initialize(self, duration, idle=False, init_delay=0, min_suspend=0,
 | |
|                    min_resume=5, max_resume_window=3, check_connection=True,
 | |
|                    suspend_iterations=None, suspend_state='',
 | |
|                    modemfwd_workaround=False):
 | |
|         """
 | |
|         Entry point.
 | |
| 
 | |
|         @param duration: total run time of the test
 | |
|         @param idle: use sys_power.idle_suspend method.
 | |
|                 (use with dummy_IdleSuspend)
 | |
|         @param init_delay: wait this many seconds before starting the test to
 | |
|                 give parallel tests time to get started
 | |
|         @param min_suspend: suspend durations will be chosen randomly out of
 | |
|                 the interval between min_suspend and min_suspend + 3 seconds.
 | |
|         @param min_resume: minimal time in seconds between suspends.
 | |
|         @param max_resume_window: maximum range to use between suspends. i.e.,
 | |
|                 we will stay awake between min_resume and min_resume +
 | |
|                 max_resume_window seconds.
 | |
|         @param check_connection: If true, we check that the network interface
 | |
|                 used for testing is up after resume. Otherwsie we reboot.
 | |
|         @param suspend_iterations: number of times to attempt suspend.  If
 | |
|                 !=None has precedence over duration.
 | |
|         @param suspend_state: Force to suspend to a specific
 | |
|                 state ("mem" or "freeze"). If the string is empty, suspend
 | |
|                 state is left to the default pref on the system.
 | |
|         @param modemfwd_workaround: disable the modemfwd daemon as a workaround
 | |
|                 for its bad behavior during modem firmware update.
 | |
|         """
 | |
|         self._endtime = time.time()
 | |
|         if duration:
 | |
|             self._endtime += duration
 | |
|         self._init_delay = init_delay
 | |
|         self._min_suspend = min_suspend
 | |
|         self._min_resume = min_resume
 | |
|         self._max_resume_window = max_resume_window
 | |
|         self._check_connection = check_connection
 | |
|         self._suspend_iterations = suspend_iterations
 | |
|         self._suspend_state = suspend_state
 | |
|         self._modemfwd_workaround = modemfwd_workaround
 | |
|         self._method = sys_power.idle_suspend if idle else sys_power.suspend_for
 | |
| 
 | |
|     def _done(self):
 | |
|         if self._suspend_iterations != None:
 | |
|             self._suspend_iterations -= 1
 | |
|             return self._suspend_iterations < 0
 | |
|         return time.time() >= self._endtime
 | |
| 
 | |
|     def _get_default_network_interface(self):
 | |
|         iface = shill_proxy.ShillProxy().get_default_interface_name()
 | |
|         if not iface:
 | |
|             return None
 | |
|         return interface.Interface(iface)
 | |
| 
 | |
|     def run_once(self):
 | |
|         time.sleep(self._init_delay)
 | |
|         self._suspender = power_suspend.Suspender(
 | |
|                 self.resultsdir, method=self._method,
 | |
|                 suspend_state=self._suspend_state)
 | |
|         # TODO(b/164255562) Temporary workaround for misbehaved modemfwd
 | |
|         if self._modemfwd_workaround:
 | |
|             utils.stop_service('modemfwd', ignore_status=True)
 | |
|         # Find the interface which is used for most communication.
 | |
|         # We assume the interface connects to the gateway and has the lowest
 | |
|         # metric.
 | |
|         if self._check_connection:
 | |
|             iface = utils.poll_for_condition(
 | |
|                         self._get_default_network_interface,
 | |
|                         desc='Find default network interface')
 | |
|             logging.info('Found default network interface: %s', iface.name)
 | |
| 
 | |
|         while not self._done():
 | |
|             time.sleep(self._min_resume +
 | |
|                        random.randint(0, self._max_resume_window))
 | |
|             # Check the network interface to the caller is still available
 | |
|             if self._check_connection:
 | |
|                 # Give a 10 second window for the network to come back.
 | |
|                 try:
 | |
|                     utils.poll_for_condition(iface.is_link_operational,
 | |
|                                              desc='Link is operational')
 | |
|                 except utils.TimeoutError:
 | |
|                     logging.error('Link to the server gone, reboot')
 | |
|                     utils.system('reboot')
 | |
|                     # Reboot may return; raise a TestFail() to abort too, even
 | |
|                     # though the server likely won't see this.
 | |
|                     raise error.TestFail('Link is gone; rebooting')
 | |
| 
 | |
|             self._suspender.suspend(random.randint(0, 3) + self._min_suspend)
 | |
| 
 | |
| 
 | |
|     def postprocess_iteration(self):
 | |
|         if self._suspender.successes:
 | |
|             keyvals = {'suspend_iterations': len(self._suspender.successes)}
 | |
|             for key in self._suspender.successes[0]:
 | |
|                 values = [result[key] for result in self._suspender.successes]
 | |
|                 keyvals[key + '_mean'] = numpy.mean(values)
 | |
|                 keyvals[key + '_stddev'] = numpy.std(values)
 | |
|                 keyvals[key + '_min'] = numpy.amin(values)
 | |
|                 keyvals[key + '_max'] = numpy.amax(values)
 | |
|             self.write_perf_keyval(keyvals)
 | |
|         if self._suspender.failures:
 | |
|             total = len(self._suspender.failures)
 | |
|             iterations = len(self._suspender.successes) + total
 | |
|             timeout = kernel = firmware = spurious = 0
 | |
|             for failure in self._suspender.failures:
 | |
|                 if type(failure) is sys_power.SuspendTimeout: timeout += 1
 | |
|                 if type(failure) is sys_power.KernelError: kernel += 1
 | |
|                 if type(failure) is sys_power.FirmwareError: firmware += 1
 | |
|                 if type(failure) is sys_power.SpuriousWakeupError: spurious += 1
 | |
|             if total == kernel + timeout:
 | |
|                 raise error.TestWarn('%d non-fatal suspend failures in %d '
 | |
|                         'iterations (%d timeouts, %d kernel warnings)' %
 | |
|                         (total, iterations, timeout, kernel))
 | |
|             if total == 1:
 | |
|                 # just throw it as is, makes aggregation on dashboards easier
 | |
|                 raise self._suspender.failures[0]
 | |
|             raise error.TestFail('%d suspend failures in %d iterations (%d '
 | |
|                     'timeouts, %d kernel warnings, %d firmware errors, %d '
 | |
|                     'spurious wakeups)' %
 | |
|                     (total, iterations, timeout, kernel, firmware, spurious))
 | |
| 
 | |
| 
 | |
|     def cleanup(self):
 | |
|         """
 | |
|         Clean this up before we wait ages for all the log copying to finish...
 | |
|         """
 | |
|         self._suspender.finalize()
 | |
|         if self._modemfwd_workaround:
 | |
|             utils.start_service('modemfwd', ignore_status=True)
 |