koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 1 | import abc |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 2 | import time |
| 3 | import logging |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 4 | import os.path |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 5 | import functools |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 6 | from typing import Dict, Any, List, Tuple |
koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 7 | |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 8 | from concurrent.futures import ThreadPoolExecutor |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 9 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 10 | from ..utils import Barrier, StopTestError |
| 11 | from ..statistic import data_property |
| 12 | from ..ssh_utils import copy_paths |
| 13 | from ..inode import INode |
| 14 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 15 | |
| 16 | |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 17 | logger = logging.getLogger("wally") |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 18 | |
| 19 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 20 | class TestConfig: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 21 | """ |
| 22 | this class describe test input configuration |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 23 | |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 24 | test_type:str - test type name |
| 25 | params:{str:Any} - parameters from yaml file for this test |
| 26 | test_uuid:str - UUID to be used to create filenames and Co |
| 27 | log_directory:str - local directory to store results |
| 28 | nodes:[Node] - node to run tests on |
| 29 | remote_dir:str - directory on nodes to be used for local files |
| 30 | """ |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 31 | def __init__(self, |
| 32 | test_type: str, |
| 33 | params: Dict[str, Any], |
| 34 | test_uuid: str, |
| 35 | nodes: List[INode], |
| 36 | log_directory: str, |
| 37 | remote_dir: str): |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 38 | self.test_type = test_type |
| 39 | self.params = params |
| 40 | self.test_uuid = test_uuid |
| 41 | self.log_directory = log_directory |
| 42 | self.nodes = nodes |
| 43 | self.remote_dir = remote_dir |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 44 | |
| 45 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 46 | class TestResults: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 47 | """ |
| 48 | this class describe test results |
| 49 | |
| 50 | config:TestConfig - test config object |
| 51 | params:dict - parameters from yaml file for this test |
| 52 | results:{str:MeasurementMesh} - test results object |
| 53 | raw_result:Any - opaque object to store raw results |
| 54 | run_interval:(float, float) - test tun time, used for sensors |
| 55 | """ |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 56 | def __init__(self, |
| 57 | config: TestConfig, |
| 58 | results: Dict[str, Any], |
| 59 | raw_result: Any, |
| 60 | run_interval: Tuple[float, float]): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 61 | self.config = config |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 62 | self.params = config.params |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 63 | self.results = results |
| 64 | self.raw_result = raw_result |
| 65 | self.run_interval = run_interval |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 66 | |
| 67 | def __str__(self): |
| 68 | res = "{0}({1}):\n results:\n".format( |
| 69 | self.__class__.__name__, |
| 70 | self.summary()) |
| 71 | |
| 72 | for name, val in self.results.items(): |
| 73 | res += " {0}={1}\n".format(name, val) |
| 74 | |
| 75 | res += " params:\n" |
| 76 | |
| 77 | for name, val in self.params.items(): |
| 78 | res += " {0}={1}\n".format(name, val) |
| 79 | |
| 80 | return res |
| 81 | |
| 82 | @abc.abstractmethod |
| 83 | def summary(self): |
| 84 | pass |
| 85 | |
| 86 | @abc.abstractmethod |
| 87 | def get_yamable(self): |
| 88 | pass |
koder aka kdanilov | e21d747 | 2015-02-14 19:02:04 -0800 | [diff] [blame] | 89 | |
| 90 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 91 | # class MeasurementMatrix: |
| 92 | # """ |
| 93 | # data:[[MeasurementResult]] - VM_COUNT x TH_COUNT matrix of MeasurementResult |
| 94 | # """ |
| 95 | # def __init__(self, data, connections_ids): |
| 96 | # self.data = data |
| 97 | # self.connections_ids = connections_ids |
| 98 | # |
| 99 | # def per_vm(self): |
| 100 | # return self.data |
| 101 | # |
| 102 | # def per_th(self): |
| 103 | # return sum(self.data, []) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 104 | |
| 105 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 106 | class MeasurementResults: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 107 | def stat(self): |
| 108 | return data_property(self.data) |
| 109 | |
| 110 | def __str__(self): |
| 111 | return 'TS([' + ", ".join(map(str, self.data)) + '])' |
| 112 | |
| 113 | |
| 114 | class SimpleVals(MeasurementResults): |
| 115 | """ |
| 116 | data:[float] - list of values |
| 117 | """ |
| 118 | def __init__(self, data): |
| 119 | self.data = data |
| 120 | |
| 121 | |
| 122 | class TimeSeriesValue(MeasurementResults): |
| 123 | """ |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 124 | data:[(float, float, float)] - list of (start_time, lenght, average_value_for_interval) |
| 125 | odata: original values |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 126 | """ |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 127 | def __init__(self, data: List[Tuple[float, float, float]]): |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 128 | assert len(data) > 0 |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 129 | self.odata = data[:] |
| 130 | self.data = [] |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 131 | |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 132 | cstart = 0 |
| 133 | for nstart, nval in data: |
| 134 | self.data.append((cstart, nstart - cstart, nval)) |
| 135 | cstart = nstart |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 136 | |
| 137 | @property |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 138 | def values(self) -> List[float]: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 139 | return [val[2] for val in self.data] |
| 140 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 141 | def average_interval(self) -> float: |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 142 | return float(sum([val[1] for val in self.data])) / len(self.data) |
| 143 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 144 | def skip(self, seconds) -> 'TimeSeriesValue': |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 145 | nres = [] |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 146 | for start, ln, val in self.data: |
| 147 | nstart = start + ln - seconds |
| 148 | if nstart > 0: |
| 149 | nres.append([nstart, val]) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 150 | return self.__class__(nres) |
| 151 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 152 | def derived(self, tdelta) -> 'TimeSeriesValue': |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 153 | end = self.data[-1][0] + self.data[-1][1] |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 154 | tdelta = float(tdelta) |
| 155 | |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 156 | ln = end / tdelta |
| 157 | |
| 158 | if ln - int(ln) > 0: |
| 159 | ln += 1 |
| 160 | |
| 161 | res = [[tdelta * i, 0.0] for i in range(int(ln))] |
| 162 | |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 163 | for start, lenght, val in self.data: |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 164 | start_idx = int(start / tdelta) |
| 165 | end_idx = int((start + lenght) / tdelta) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 166 | |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 167 | for idx in range(start_idx, end_idx + 1): |
| 168 | rstart = tdelta * idx |
| 169 | rend = tdelta * (idx + 1) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 170 | |
koder aka kdanilov | bb6d6cd | 2015-06-20 02:55:07 +0300 | [diff] [blame] | 171 | intersection_ln = min(rend, start + lenght) - max(start, rstart) |
| 172 | if intersection_ln > 0: |
| 173 | try: |
| 174 | res[idx][1] += val * intersection_ln / tdelta |
| 175 | except IndexError: |
| 176 | raise |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 177 | |
| 178 | return self.__class__(res) |
| 179 | |
| 180 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 181 | class PerfTest: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 182 | """ |
| 183 | Very base class for tests |
| 184 | config:TestConfig - test configuration |
| 185 | stop_requested:bool - stop for test requested |
| 186 | """ |
| 187 | def __init__(self, config): |
| 188 | self.config = config |
koder aka kdanilov | e2de58c | 2015-04-24 22:59:36 +0300 | [diff] [blame] | 189 | self.stop_requested = False |
| 190 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 191 | def request_stop(self) -> None: |
koder aka kdanilov | e2de58c | 2015-04-24 22:59:36 +0300 | [diff] [blame] | 192 | self.stop_requested = True |
koder aka kdanilov | 2066daf | 2015-04-23 21:05:41 +0300 | [diff] [blame] | 193 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 194 | def join_remote(self, path: str) -> str: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 195 | return os.path.join(self.config.remote_dir, path) |
koder aka kdanilov | 4500a5f | 2015-04-17 16:55:17 +0300 | [diff] [blame] | 196 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 197 | @classmethod |
| 198 | @abc.abstractmethod |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 199 | def load(cls, path: str): |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 200 | pass |
| 201 | |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 202 | @abc.abstractmethod |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 203 | def run(self) -> List[TestResults]: |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 204 | pass |
| 205 | |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 206 | @abc.abstractmethod |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 207 | def format_for_console(cls, data: Any) -> str: |
koder aka kdanilov | ec1b973 | 2015-04-23 20:43:29 +0300 | [diff] [blame] | 208 | pass |
| 209 | |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 210 | |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 211 | class ThreadedTest(PerfTest): |
| 212 | """ |
| 213 | Base class for tests, which spawn separated thread for each node |
| 214 | """ |
| 215 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 216 | def run(self) -> List[TestResults]: |
Yulia Portnova | b0c977c | 2015-12-11 19:23:28 +0200 | [diff] [blame] | 217 | barrier = Barrier(len(self.config.nodes)) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 218 | th_test_func = functools.partial(self.th_test_func, barrier) |
| 219 | |
Yulia Portnova | b0c977c | 2015-12-11 19:23:28 +0200 | [diff] [blame] | 220 | with ThreadPoolExecutor(len(self.config.nodes)) as pool: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 221 | return list(pool.map(th_test_func, self.config.nodes)) |
| 222 | |
| 223 | @abc.abstractmethod |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 224 | def do_test(self, node: INode) -> TestResults: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 225 | pass |
| 226 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 227 | def th_test_func(self, barrier: Barrier, node: INode) -> TestResults: |
| 228 | test_name = self.__class__.__name__ |
| 229 | logger.debug("Starting {} test on {}".format(test_name , node)) |
| 230 | logger.debug("Run test preparation on {}".format(node)) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 231 | self.pre_run(node) |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 232 | |
| 233 | # wait till all thread became ready |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 234 | barrier.wait() |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 235 | |
| 236 | logger.debug("Run test on {}".format(node)) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 237 | try: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 238 | return self.do_test(node) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 239 | except Exception as exc: |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 240 | msg = "In test {} for {}".format(test_name, node) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 241 | logger.exception(msg) |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 242 | raise StopTestError(msg) from exc |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 243 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 244 | def pre_run(self, node: INode) -> None: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 245 | pass |
| 246 | |
| 247 | |
| 248 | class TwoScriptTest(ThreadedTest): |
| 249 | def __init__(self, *dt, **mp): |
| 250 | ThreadedTest.__init__(self, *dt, **mp) |
Yulia Portnova | b0c977c | 2015-12-11 19:23:28 +0200 | [diff] [blame] | 251 | self.remote_dir = '/tmp' |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 252 | self.prerun_script = self.config.params['prerun_script'] |
| 253 | self.run_script = self.config.params['run_script'] |
| 254 | |
| 255 | self.prerun_tout = self.config.params.get('prerun_tout', 3600) |
| 256 | self.run_tout = self.config.params.get('run_tout', 3600) |
Yulia Portnova | 7ddfa73 | 2015-02-24 17:32:58 +0200 | [diff] [blame] | 257 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 258 | def get_remote_for_script(self, script: str) -> str: |
| 259 | return os.path.join(self.remote_dir, os.path.basename(script)) |
Yulia Portnova | 7ddfa73 | 2015-02-24 17:32:58 +0200 | [diff] [blame] | 260 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 261 | def pre_run(self, node: INode) -> None: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 262 | copy_paths(node.connection, |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 263 | {self.run_script: self.get_remote_for_script(self.run_script), |
| 264 | self.prerun_script: self.get_remote_for_script(self.prerun_script)}) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 265 | |
Yulia Portnova | b0c977c | 2015-12-11 19:23:28 +0200 | [diff] [blame] | 266 | cmd = self.get_remote_for_script(self.prerun_script) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 267 | cmd += ' ' + self.config.params.get('prerun_opts', '') |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 268 | node.run(cmd, timeout=self.prerun_tout) |
Yulia Portnova | 7ddfa73 | 2015-02-24 17:32:58 +0200 | [diff] [blame] | 269 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 270 | def do_test(self, node: INode) -> TestResults: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 271 | cmd = self.get_remote_for_script(self.run_script) |
| 272 | cmd += ' ' + self.config.params.get('run_opts', '') |
| 273 | t1 = time.time() |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 274 | res = node.run(cmd, timeout=self.run_tout) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 275 | t2 = time.time() |
| 276 | return TestResults(self.config, None, res, (t1, t2)) |