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