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)
|