Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 1 | # Copyright (c) 2012 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 | |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 5 | import logging |
| 6 | import sys |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 7 | import threading |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 8 | import time |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 9 | from autotest_lib.client.common_lib import error |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 10 | |
| 11 | |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 12 | class BaseStressor(threading.Thread): |
| 13 | """ |
| 14 | Implements common functionality for *Stressor classes. |
| 15 | |
| 16 | @var stressor: callable which performs a single stress event. |
| 17 | """ |
Craig Harrison | 9a04f95 | 2012-11-20 15:49:46 -0800 | [diff] [blame] | 18 | def __init__(self, stressor, on_exit=None, escalate_exceptions=True): |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 19 | """ |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 20 | Initialize the ControlledStressor. |
| 21 | |
| 22 | @param stressor: callable which performs a single stress event. |
Craig Harrison | 9a04f95 | 2012-11-20 15:49:46 -0800 | [diff] [blame] | 23 | @param on_exit: callable which will be called when the thread finishes. |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 24 | @param escalate_exceptions: whether to escalate exceptions to the parent |
| 25 | thread; defaults to True. |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 26 | """ |
| 27 | super(BaseStressor, self).__init__() |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 28 | self.daemon = True |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 29 | self.stressor = stressor |
Craig Harrison | 9a04f95 | 2012-11-20 15:49:46 -0800 | [diff] [blame] | 30 | self.on_exit = on_exit |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 31 | self._escalate_exceptions = escalate_exceptions |
| 32 | self._exc_info = None |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 33 | |
| 34 | |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 35 | def start(self, start_condition=None, start_timeout_secs=None): |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 36 | """ |
| 37 | Creates a new thread which will call the run() method. |
| 38 | |
| 39 | Optionally takes a wait condition before the stressor loop. Returns |
| 40 | immediately. |
| 41 | |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 42 | @param start_condition: the new thread will wait until this optional |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 43 | callable returns True before running the stressor. |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 44 | @param start_timeout_secs: how long to wait for |start_condition| to |
| 45 | become True, or None to wait forever. |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 46 | """ |
| 47 | self._start_condition = start_condition |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 48 | self._start_timeout_secs = start_timeout_secs |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 49 | super(BaseStressor, self).start() |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 50 | |
| 51 | |
| 52 | def run(self): |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 53 | """ |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 54 | Wait for |_start_condition|, and then start the stressor loop. |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 55 | |
| 56 | Overloaded from threading.Thread. This is run in a separate thread when |
| 57 | start() is called. |
| 58 | """ |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 59 | try: |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 60 | self._wait_for_start_condition() |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 61 | self._loop_stressor() |
| 62 | except Exception as e: |
| 63 | if self._escalate_exceptions: |
| 64 | self._exc_info = sys.exc_info() |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 65 | raise # Terminates this thread. Caller continues to run. |
Craig Harrison | 9a04f95 | 2012-11-20 15:49:46 -0800 | [diff] [blame] | 66 | finally: |
| 67 | if self.on_exit: |
| 68 | self.on_exit() |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 69 | |
| 70 | |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 71 | def _wait_for_start_condition(self): |
| 72 | """ |
| 73 | Loop until _start_condition() returns True, or _start_timeout_secs |
| 74 | have elapsed. |
| 75 | |
| 76 | @raise error.TestFail if we time out waiting for the start condition |
| 77 | """ |
| 78 | if self._start_condition is None: |
| 79 | return |
| 80 | |
| 81 | elapsed_secs = 0 |
| 82 | while not self._start_condition(): |
| 83 | if (self._start_timeout_secs and |
| 84 | elapsed_secs >= self._start_timeout_secs): |
| 85 | raise error.TestFail('start condition did not become true ' |
| 86 | 'within %d seconds' % |
| 87 | self._start_timeout_secs) |
| 88 | time.sleep(1) |
| 89 | elapsed_secs += 1 |
| 90 | |
| 91 | |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 92 | def _loop_stressor(self): |
| 93 | """ |
| 94 | Apply stressor in a loop. |
| 95 | |
| 96 | Overloaded by the particular *Stressor. |
| 97 | """ |
| 98 | raise NotImplementedError |
| 99 | |
| 100 | |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 101 | def reraise(self): |
| 102 | """ |
| 103 | Reraise an exception raised in the thread's stress loop. |
| 104 | |
| 105 | This is a No-op if no exception was raised. |
| 106 | """ |
| 107 | if self._exc_info: |
| 108 | exc_info = self._exc_info |
| 109 | self._exc_info = None |
| 110 | raise exc_info[0], exc_info[1], exc_info[2] |
| 111 | |
| 112 | |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 113 | class ControlledStressor(BaseStressor): |
| 114 | """ |
| 115 | Run a stressor in loop on a separate thread. |
| 116 | |
| 117 | Creates a new thread and calls |stressor| in a loop until stop() is called. |
| 118 | """ |
Craig Harrison | 9a04f95 | 2012-11-20 15:49:46 -0800 | [diff] [blame] | 119 | def __init__(self, stressor, on_exit=None, escalate_exceptions=True): |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 120 | """ |
| 121 | Initialize the ControlledStressor. |
| 122 | |
| 123 | @param stressor: callable which performs a single stress event. |
Craig Harrison | 9a04f95 | 2012-11-20 15:49:46 -0800 | [diff] [blame] | 124 | @param on_exit: callable which will be called when the thread finishes. |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 125 | @param escalate_exceptions: whether to escalate exceptions to the parent |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 126 | thread when stop() is called; defaults to True. |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 127 | """ |
| 128 | self._complete = threading.Event() |
Craig Harrison | 9a04f95 | 2012-11-20 15:49:46 -0800 | [diff] [blame] | 129 | super(ControlledStressor, self).__init__(stressor, on_exit, |
| 130 | escalate_exceptions) |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 131 | |
| 132 | |
| 133 | def _loop_stressor(self): |
| 134 | """Overloaded from parent.""" |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 135 | iteration_num = 0 |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 136 | while not self._complete.is_set(): |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 137 | iteration_num += 1 |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 138 | logging.info('Stressor iteration: %d', iteration_num) |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 139 | self.stressor() |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 140 | |
| 141 | |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 142 | def start(self, start_condition=None, start_timeout_secs=None): |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 143 | """Start applying the stressor. |
| 144 | |
| 145 | Overloaded from parent. |
| 146 | |
| 147 | @param start_condition: the new thread will wait to until this optional |
| 148 | callable returns True before running the stressor. |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 149 | @param start_timeout_secs: how long to wait for |start_condition| to |
| 150 | become True, or None to wait forever. |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 151 | """ |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 152 | self._complete.clear() |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 153 | super(ControlledStressor, self).start(start_condition, |
| 154 | start_timeout_secs) |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 155 | |
| 156 | |
| 157 | def stop(self, timeout=45): |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 158 | """ |
| 159 | Stop applying the stressor. |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 160 | |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 161 | @param timeout: maximum time to wait for a single run of the stressor to |
| 162 | complete, defaults to 45 seconds. |
| 163 | """ |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 164 | self._complete.set() |
| 165 | self.join(timeout) |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 166 | self.reraise() |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 167 | |
| 168 | |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 169 | class CountedStressor(BaseStressor): |
| 170 | """ |
| 171 | Run a stressor in a loop on a separate thread a given number of times. |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 172 | |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 173 | Creates a new thread and calls |stressor| in a loop |iterations| times. The |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 174 | calling thread can use wait() to block until the loop completes. If the |
| 175 | stressor thread terminates with an exception, wait() will propagate that |
| 176 | exception to the thread that called wait(). |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 177 | """ |
| 178 | def _loop_stressor(self): |
| 179 | """Overloaded from parent.""" |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 180 | for iteration_num in xrange(1, self._iterations + 1): |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 181 | logging.info('Stressor iteration: %d of %d', |
| 182 | iteration_num, self._iterations) |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 183 | self.stressor() |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 184 | |
| 185 | |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 186 | def start(self, iterations, start_condition=None, start_timeout_secs=None): |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 187 | """ |
| 188 | Apply the stressor a given number of times. |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 189 | |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 190 | Overloaded from parent. |
| 191 | |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 192 | @param iterations: number of times to apply the stressor. |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 193 | @param start_condition: the new thread will wait to until this optional |
| 194 | callable returns True before running the stressor. |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 195 | @param start_timeout_secs: how long to wait for |start_condition| to |
| 196 | become True, or None to wait forever. |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 197 | """ |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 198 | self._iterations = iterations |
mukesh agrawal | 8487347 | 2015-05-15 14:37:59 -0700 | [diff] [blame^] | 199 | super(CountedStressor, self).start(start_condition, start_timeout_secs) |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 200 | |
| 201 | |
| 202 | def wait(self, timeout=None): |
| 203 | """Wait until the stressor completes. |
| 204 | |
Craig Harrison | fc459df | 2012-10-04 13:49:48 -0700 | [diff] [blame] | 205 | @param timeout: maximum time for the thread to complete, by default |
| 206 | never times out. |
Craig Harrison | e3ea8f2 | 2012-10-01 13:55:21 -0700 | [diff] [blame] | 207 | """ |
| 208 | self.join(timeout) |
Craig Harrison | 922fcb9 | 2012-10-31 09:06:22 -0700 | [diff] [blame] | 209 | self.reraise() |