You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
216 lines
7.1 KiB
216 lines
7.1 KiB
# Lint as: python2, python3
|
|
# Copyright (c) 2012 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.
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
import logging
|
|
import sys
|
|
import threading
|
|
import time
|
|
from autotest_lib.client.common_lib import error
|
|
import six
|
|
from six.moves import range
|
|
|
|
|
|
class BaseStressor(threading.Thread):
|
|
"""
|
|
Implements common functionality for *Stressor classes.
|
|
|
|
@var stressor: callable which performs a single stress event.
|
|
"""
|
|
def __init__(self, stressor, on_exit=None, escalate_exceptions=True):
|
|
"""
|
|
Initialize the ControlledStressor.
|
|
|
|
@param stressor: callable which performs a single stress event.
|
|
@param on_exit: callable which will be called when the thread finishes.
|
|
@param escalate_exceptions: whether to escalate exceptions to the parent
|
|
thread; defaults to True.
|
|
"""
|
|
super(BaseStressor, self).__init__()
|
|
self.daemon = True
|
|
self.stressor = stressor
|
|
self.on_exit = on_exit
|
|
self._escalate_exceptions = escalate_exceptions
|
|
self._exc_info = None
|
|
|
|
|
|
def start(self, start_condition=None, start_timeout_secs=None):
|
|
"""
|
|
Creates a new thread which will call the run() method.
|
|
|
|
Optionally takes a wait condition before the stressor loop. Returns
|
|
immediately.
|
|
|
|
@param start_condition: the new thread will wait until this optional
|
|
callable returns True before running the stressor.
|
|
@param start_timeout_secs: how long to wait for |start_condition| to
|
|
become True, or None to wait forever.
|
|
"""
|
|
self._start_condition = start_condition
|
|
self._start_timeout_secs = start_timeout_secs
|
|
super(BaseStressor, self).start()
|
|
|
|
|
|
def run(self):
|
|
"""
|
|
Wait for |_start_condition|, and then start the stressor loop.
|
|
|
|
Overloaded from threading.Thread. This is run in a separate thread when
|
|
start() is called.
|
|
"""
|
|
try:
|
|
self._wait_for_start_condition()
|
|
self._loop_stressor()
|
|
except Exception as e:
|
|
if self._escalate_exceptions:
|
|
self._exc_info = sys.exc_info()
|
|
raise # Terminates this thread. Caller continues to run.
|
|
finally:
|
|
if self.on_exit:
|
|
self.on_exit()
|
|
|
|
|
|
def _wait_for_start_condition(self):
|
|
"""
|
|
Loop until _start_condition() returns True, or _start_timeout_secs
|
|
have elapsed.
|
|
|
|
@raise error.TestFail if we time out waiting for the start condition
|
|
"""
|
|
if self._start_condition is None:
|
|
return
|
|
|
|
elapsed_secs = 0
|
|
while not self._start_condition():
|
|
if (self._start_timeout_secs and
|
|
elapsed_secs >= self._start_timeout_secs):
|
|
raise error.TestFail('start condition did not become true '
|
|
'within %d seconds' %
|
|
self._start_timeout_secs)
|
|
time.sleep(1)
|
|
elapsed_secs += 1
|
|
|
|
|
|
def _loop_stressor(self):
|
|
"""
|
|
Apply stressor in a loop.
|
|
|
|
Overloaded by the particular *Stressor.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def reraise(self):
|
|
"""
|
|
Reraise an exception raised in the thread's stress loop.
|
|
|
|
This is a No-op if no exception was raised.
|
|
"""
|
|
if self._exc_info:
|
|
exc_info = self._exc_info
|
|
self._exc_info = None
|
|
six.reraise(exc_info[0], exc_info[1], exc_info[2])
|
|
|
|
|
|
class ControlledStressor(BaseStressor):
|
|
"""
|
|
Run a stressor in loop on a separate thread.
|
|
|
|
Creates a new thread and calls |stressor| in a loop until stop() is called.
|
|
"""
|
|
def __init__(self, stressor, on_exit=None, escalate_exceptions=True):
|
|
"""
|
|
Initialize the ControlledStressor.
|
|
|
|
@param stressor: callable which performs a single stress event.
|
|
@param on_exit: callable which will be called when the thread finishes.
|
|
@param escalate_exceptions: whether to escalate exceptions to the parent
|
|
thread when stop() is called; defaults to True.
|
|
"""
|
|
self._complete = threading.Event()
|
|
super(ControlledStressor, self).__init__(stressor, on_exit,
|
|
escalate_exceptions)
|
|
|
|
|
|
def _loop_stressor(self):
|
|
"""Overloaded from parent."""
|
|
iteration_num = 0
|
|
while not self._complete.is_set():
|
|
iteration_num += 1
|
|
logging.info('Stressor iteration: %d', iteration_num)
|
|
self.stressor()
|
|
|
|
|
|
def start(self, start_condition=None, start_timeout_secs=None):
|
|
"""Start applying the stressor.
|
|
|
|
Overloaded from parent.
|
|
|
|
@param start_condition: the new thread will wait to until this optional
|
|
callable returns True before running the stressor.
|
|
@param start_timeout_secs: how long to wait for |start_condition| to
|
|
become True, or None to wait forever.
|
|
"""
|
|
self._complete.clear()
|
|
super(ControlledStressor, self).start(start_condition,
|
|
start_timeout_secs)
|
|
|
|
|
|
def stop(self, timeout=45):
|
|
"""
|
|
Stop applying the stressor.
|
|
|
|
@param timeout: maximum time to wait for a single run of the stressor to
|
|
complete, defaults to 45 seconds.
|
|
"""
|
|
self._complete.set()
|
|
self.join(timeout)
|
|
self.reraise()
|
|
|
|
|
|
class CountedStressor(BaseStressor):
|
|
"""
|
|
Run a stressor in a loop on a separate thread a given number of times.
|
|
|
|
Creates a new thread and calls |stressor| in a loop |iterations| times. The
|
|
calling thread can use wait() to block until the loop completes. If the
|
|
stressor thread terminates with an exception, wait() will propagate that
|
|
exception to the thread that called wait().
|
|
"""
|
|
def _loop_stressor(self):
|
|
"""Overloaded from parent."""
|
|
for iteration_num in range(1, self._iterations + 1):
|
|
logging.info('Stressor iteration: %d of %d',
|
|
iteration_num, self._iterations)
|
|
self.stressor()
|
|
|
|
|
|
def start(self, iterations, start_condition=None, start_timeout_secs=None):
|
|
"""
|
|
Apply the stressor a given number of times.
|
|
|
|
Overloaded from parent.
|
|
|
|
@param iterations: number of times to apply the stressor.
|
|
@param start_condition: the new thread will wait to until this optional
|
|
callable returns True before running the stressor.
|
|
@param start_timeout_secs: how long to wait for |start_condition| to
|
|
become True, or None to wait forever.
|
|
"""
|
|
self._iterations = iterations
|
|
super(CountedStressor, self).start(start_condition, start_timeout_secs)
|
|
|
|
|
|
def wait(self, timeout=None):
|
|
"""Wait until the stressor completes.
|
|
|
|
@param timeout: maximum time for the thread to complete, by default
|
|
never times out.
|
|
"""
|
|
self.join(timeout)
|
|
self.reraise()
|