koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 1 | import re |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 2 | import abc |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 3 | import time |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 4 | import array |
| 5 | import struct |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 6 | import logging |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 7 | import os.path |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 8 | import datetime |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 9 | from typing import Any, List, Optional, Callable, cast, Iterator, Tuple, Iterable |
koder aka kdanilov | 652cd80 | 2015-04-13 12:21:07 +0300 | [diff] [blame] | 10 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 11 | from concurrent.futures import ThreadPoolExecutor, wait, Future |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 12 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 13 | from ..utils import StopTestError, sec_to_str, get_time_interval_printable_info |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 14 | from ..node_interfaces import IRPCNode |
koder aka kdanilov | 3d2bc4f | 2016-11-12 18:31:18 +0200 | [diff] [blame] | 15 | from ..storage import Storage |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 16 | from ..result_classes import TestSuiteConfig, TestJobConfig, JobMetrics, TimeSeries |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 17 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 18 | |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 19 | logger = logging.getLogger("wally") |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 20 | |
| 21 | |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 22 | __doc__ = "Contains base classes for performance tests" |
| 23 | |
| 24 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 25 | class ResultStorage: |
| 26 | ts_header_format = "!IIIcc" |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 27 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 28 | def __init__(self, storage: Storage, job_config_cls: type) -> None: |
koder aka kdanilov | 3d2bc4f | 2016-11-12 18:31:18 +0200 | [diff] [blame] | 29 | self.storage = storage |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 30 | self.job_config_cls = job_config_cls |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 31 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 32 | def get_suite_root(self, suite_type: str, idx: int) -> str: |
| 33 | return "results/{}_{}".format(suite_type, idx) |
koder aka kdanilov | 88407ff | 2015-05-26 15:35:57 +0300 | [diff] [blame] | 34 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 35 | def get_job_root(self, suite_root: str, summary: str, run_id: int) -> str: |
| 36 | return "{}/{}_{}".format(suite_root, summary, run_id) |
| 37 | |
| 38 | # store |
| 39 | def put_suite_config(self, config: TestSuiteConfig, root: str) -> None: |
| 40 | self.storage.put(config, root, "config.yml") |
| 41 | |
| 42 | def put_job_config(self, config: TestJobConfig, root: str) -> None: |
| 43 | self.storage.put(config, root, "config.yml") |
| 44 | |
| 45 | def get_suite_config(self, suite_root: str) -> TestSuiteConfig: |
| 46 | return self.storage.load(TestSuiteConfig, suite_root, "config.yml") |
| 47 | |
| 48 | def get_job_node_prefix(self, job_root_path: str, node_id: str) -> str: |
| 49 | return "{}/{}".format(job_root_path, node_id) |
| 50 | |
| 51 | def get_ts_path(self, job_root_path: str, node_id: str, dev: str, sensor_name: str) -> str: |
| 52 | return "{}_{}.{}".format(self.get_job_node_prefix(job_root_path, node_id), dev, sensor_name) |
| 53 | |
| 54 | def put_ts(self, ts: TimeSeries, job_root_path: str, node_id: str, dev: str, sensor_name: str) -> None: |
| 55 | # TODO: check that 'metrics', 'dev' and 'node_id' match required patterns |
| 56 | root_path = self.get_ts_path(job_root_path, node_id, dev, sensor_name) |
| 57 | |
| 58 | if len(ts.data) / ts.second_axis_size != len(ts.times): |
| 59 | logger.error("Unbalanced time series data. Array size has % elements, while time size has %", |
| 60 | len(ts.data) / ts.second_axis_size, len(ts.times)) |
| 61 | raise StopTestError() |
| 62 | |
| 63 | with self.storage.get_fd(root_path, "cb") as fd: |
| 64 | header = struct.pack(self.ts_header_format, |
| 65 | ts.second_axis_size, |
| 66 | len(ts.data), |
| 67 | len(ts.times), |
| 68 | cast(array.array, ts.data).typecode.encode("ascii"), |
| 69 | cast(array.array, ts.times).typecode.encode("ascii")) |
| 70 | fd.write(header) |
| 71 | cast(array.array, ts.data).tofile(fd) |
| 72 | cast(array.array, ts.times).tofile(fd) |
| 73 | |
| 74 | if ts.raw is not None: |
| 75 | self.storage.put_raw(ts.raw, root_path + ":raw") |
| 76 | |
| 77 | def put_extra(self, job_root: str, node_id: str, key: str, data: bytes) -> None: |
| 78 | self.storage.put_raw(data, job_root, node_id + "_" + key) |
| 79 | |
| 80 | def list_suites(self) -> Iterator[Tuple[TestSuiteConfig, str]]: |
| 81 | """iterates over (suite_name, suite_id, suite_root_path) |
| 82 | primary this function output should be used as input into list_jobs_in_suite method |
| 83 | """ |
| 84 | ts_re = re.compile(r"[a-zA-Z]+_\d+$") |
| 85 | for is_file, name in self.storage.list("results"): |
| 86 | if not is_file: |
| 87 | rr = ts_re.match(name) |
| 88 | if rr: |
| 89 | path = "results/" + name |
| 90 | yield self.get_suite_config(path), path |
| 91 | |
| 92 | def list_jobs_in_suite(self, suite_root_path: str) -> Iterator[Tuple[TestJobConfig, str, int]]: |
| 93 | """iterates over (job_summary, job_root_path) |
| 94 | primary this function output should be used as input into list_ts_in_job method |
| 95 | """ |
| 96 | ts_re = re.compile(r"(?P<job_summary>[a-zA-Z0-9]+)_(?P<id>\d+)$") |
| 97 | for is_file, name in self.storage.list(suite_root_path): |
| 98 | if is_file: |
| 99 | continue |
| 100 | rr = ts_re.match(name) |
| 101 | if rr: |
| 102 | config_path = "{}/{}/config.yml".format(suite_root_path, name) |
| 103 | if config_path in self.storage: |
| 104 | cfg = self.storage.load(self.job_config_cls, config_path) |
| 105 | yield cfg, "{}/{}".format(suite_root_path, name), int(rr.group("id")) |
| 106 | |
| 107 | def list_ts_in_job(self, job_root_path: str) -> Iterator[Tuple[str, str, str]]: |
| 108 | """iterates over (node_id, device_name, sensor_name) |
| 109 | primary this function output should be used as input into load_ts method |
| 110 | """ |
| 111 | # TODO: check that all TS files available |
| 112 | ts_re = re.compile(r"(?P<node_id>\d+\.\d+\.\d+\.\d+:\d+)_(?P<dev>[^.]+)\.(?P<sensor>[a-z_]+)$") |
| 113 | already_found = set() |
| 114 | for is_file, name in self.storage.list(job_root_path): |
| 115 | if not is_file: |
| 116 | continue |
| 117 | rr = ts_re.match(name) |
| 118 | if rr: |
| 119 | key = (rr.group("node_id"), rr.group("dev"), rr.group("sensor")) |
| 120 | if key not in already_found: |
| 121 | already_found.add(key) |
| 122 | yield key |
| 123 | |
| 124 | def load_ts(self, root_path: str, node_id: str, dev: str, sensor_name: str) -> TimeSeries: |
| 125 | path = self.get_ts_path(root_path, node_id, dev, sensor_name) |
| 126 | |
| 127 | with self.storage.get_fd(path, "rb") as fd: |
| 128 | header = fd.read(struct.calcsize(self.ts_header_format)) |
| 129 | second_axis_size, data_sz, time_sz, data_typecode, time_typecode = \ |
| 130 | struct.unpack(self.ts_header_format, header) |
| 131 | |
| 132 | data = array.array(data_typecode.decode("ascii")) |
| 133 | times = array.array(time_typecode.decode("ascii")) |
| 134 | |
| 135 | data.fromfile(fd, data_sz) |
| 136 | times.fromfile(fd, time_sz) |
| 137 | |
| 138 | # calculate number of elements |
| 139 | return TimeSeries("{}.{}".format(dev, sensor_name), |
| 140 | raw=None, |
| 141 | data=data, |
| 142 | times=times, |
| 143 | second_axis_size=second_axis_size) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 144 | |
| 145 | |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 146 | class PerfTest(metaclass=abc.ABCMeta): |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 147 | """Base class for all tests""" |
| 148 | name = None # type: str |
| 149 | max_retry = 3 |
| 150 | retry_time = 30 |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 151 | job_config_cls = None # type: type |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 152 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 153 | def __init__(self, storage: Storage, config: TestSuiteConfig, idx: int, on_idle: Callable[[], None] = None) -> None: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 154 | self.config = config |
koder aka kdanilov | e2de58c | 2015-04-24 22:59:36 +0300 | [diff] [blame] | 155 | self.stop_requested = False |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 156 | self.sorted_nodes_ids = sorted(node.info.node_id() for node in self.config.nodes) |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 157 | self.on_idle = on_idle |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 158 | self.storage = storage |
| 159 | self.rstorage = ResultStorage(self.storage, self.job_config_cls) |
| 160 | self.idx = idx |
koder aka kdanilov | e2de58c | 2015-04-24 22:59:36 +0300 | [diff] [blame] | 161 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 162 | def request_stop(self) -> None: |
koder aka kdanilov | e2de58c | 2015-04-24 22:59:36 +0300 | [diff] [blame] | 163 | self.stop_requested = True |
koder aka kdanilov | 2066daf | 2015-04-23 21:05:41 +0300 | [diff] [blame] | 164 | |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 165 | def join_remote(self, path: str) -> str: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 166 | return os.path.join(self.config.remote_dir, path) |
koder aka kdanilov | 4500a5f | 2015-04-17 16:55:17 +0300 | [diff] [blame] | 167 | |
koder aka kdanilov | 4af1c1d | 2015-05-18 15:48:58 +0300 | [diff] [blame] | 168 | @abc.abstractmethod |
koder aka kdanilov | bbbe1dc | 2016-12-20 01:19:56 +0200 | [diff] [blame] | 169 | def run(self) -> None: |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 170 | pass |
| 171 | |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 172 | @abc.abstractmethod |
koder aka kdanilov | 39e449e | 2016-12-17 15:15:26 +0200 | [diff] [blame] | 173 | def format_for_console(self, data: Any) -> str: |
koder aka kdanilov | ec1b973 | 2015-04-23 20:43:29 +0300 | [diff] [blame] | 174 | pass |
| 175 | |
koder aka kdanilov | 4643fd6 | 2015-02-10 16:20:13 -0800 | [diff] [blame] | 176 | |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 177 | class ThreadedTest(PerfTest, metaclass=abc.ABCMeta): |
| 178 | """Base class for tests, which spawn separated thread for each node""" |
| 179 | |
| 180 | # max allowed time difference between starts and stops of run of the same test on different test nodes |
| 181 | # used_max_diff = max((min_run_time * max_rel_time_diff), max_time_diff) |
| 182 | max_time_diff = 5 |
| 183 | max_rel_time_diff = 0.05 |
koder aka kdanilov | ffaf48d | 2016-12-27 02:25:29 +0200 | [diff] [blame] | 184 | load_profile_name = None # type: str |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 185 | |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 186 | def __init__(self, *args, **kwargs) -> None: |
| 187 | PerfTest.__init__(self, *args, **kwargs) |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 188 | self.job_configs = [None] # type: List[Optional[TestJobConfig]] |
| 189 | self.suite_root_path = self.rstorage.get_suite_root(self.config.test_type, self.idx) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 190 | |
| 191 | @abc.abstractmethod |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 192 | def get_expected_runtime(self, iter_cfg: TestJobConfig) -> Optional[int]: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 193 | pass |
| 194 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 195 | def get_not_done_stages(self) -> Iterable[Tuple[int, TestJobConfig]]: |
| 196 | all_jobs = dict(enumerate(self.job_configs)) |
| 197 | for db_config, path, jid in self.rstorage.list_jobs_in_suite(self.suite_root_path): |
| 198 | if jid in all_jobs: |
| 199 | job_config = all_jobs[jid] |
| 200 | if job_config != db_config: |
| 201 | logger.error("Test info at path '%s/config' is not equal to expected config for iteration %s.%s." + |
| 202 | " Maybe configuration was changed before test was restarted. " + |
| 203 | "DB cfg is:\n %s\nExpected cfg is:\n %s\nFix DB or rerun test from beginning", |
| 204 | path, self.name, job_config.summary, |
| 205 | str(db_config).replace("\n", "\n "), |
| 206 | str(job_config).replace("\n", "\n ")) |
| 207 | raise StopTestError() |
koder aka kdanilov | bbbe1dc | 2016-12-20 01:19:56 +0200 | [diff] [blame] | 208 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 209 | logger.info("Test iteration %s.%s found in storage and will be skipped", |
| 210 | self.name, job_config.summary) |
| 211 | del all_jobs[jid] |
| 212 | return all_jobs.items() |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 213 | |
koder aka kdanilov | bbbe1dc | 2016-12-20 01:19:56 +0200 | [diff] [blame] | 214 | def run(self) -> None: |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 215 | try: |
| 216 | cfg = self.rstorage.get_suite_config(self.suite_root_path) |
| 217 | except KeyError: |
| 218 | cfg = None |
| 219 | |
| 220 | if cfg is not None and cfg != self.config: |
| 221 | logger.error("Current suite %s config is not equal to found in storage at %s", |
| 222 | self.config.test_type, self.suite_root_path) |
| 223 | raise StopTestError() |
| 224 | |
| 225 | not_in_storage = list(self.get_not_done_stages()) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 226 | |
| 227 | if not not_in_storage: |
| 228 | logger.info("All test iteration in storage already. Skip test") |
| 229 | return |
| 230 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 231 | self.rstorage.put_suite_config(self.config, self.suite_root_path) |
| 232 | |
| 233 | logger.debug("Run test %s with profile %r on nodes %s.", self.name, |
| 234 | self.load_profile_name, |
| 235 | ",".join(self.sorted_nodes_ids)) |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 236 | logger.debug("Prepare nodes") |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 237 | |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 238 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 239 | with ThreadPoolExecutor(len(self.config.nodes)) as pool: |
| 240 | # config nodes |
| 241 | list(pool.map(self.config_node, self.config.nodes)) |
| 242 | |
| 243 | run_times = [self.get_expected_runtime(job_config) for _, job_config in not_in_storage] |
| 244 | |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 245 | if None not in run_times: |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 246 | # +5% - is a rough estimation for additional operations |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 247 | expected_run_time = int(sum(run_times) * 1.05) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 248 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 249 | exec_time_s, end_dt_s = get_time_interval_printable_info(expected_run_time) |
| 250 | logger.info("Entire test should takes around %s and finished at %s", exec_time_s, end_dt_s) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 251 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 252 | for run_id, job_config in not_in_storage: |
| 253 | job_path = self.rstorage.get_job_root(self.suite_root_path, job_config.summary, run_id) |
| 254 | |
| 255 | jfutures = [] # type: List[Future] |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 256 | for idx in range(self.max_retry): |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 257 | logger.debug("Prepare job %s", job_config.summary) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 258 | |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 259 | # prepare nodes for new iterations |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 260 | wait([pool.submit(self.prepare_iteration, node, job_config) for node in self.config.nodes]) |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 261 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 262 | expected_job_time = self.get_expected_runtime(job_config) |
| 263 | exec_time_s, end_dt_s = get_time_interval_printable_info(expected_job_time) |
| 264 | logger.info("Job should takes around %s and finished at %s", exec_time_s, end_dt_s) |
| 265 | |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 266 | try: |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 267 | jfutures = [] |
| 268 | for node in self.config.nodes: |
| 269 | future = pool.submit(self.run_iteration, node, job_config, job_path) |
| 270 | jfutures.append(future) |
| 271 | # test completed successfully, stop retrying |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 272 | break |
| 273 | except EnvironmentError: |
| 274 | if self.max_retry - 1 == idx: |
| 275 | logger.exception("Fio failed") |
| 276 | raise StopTestError() |
| 277 | logger.exception("During fio run") |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 278 | logger.info("Sleeping %ss and retrying job", self.retry_time) |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 279 | time.sleep(self.retry_time) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 280 | |
| 281 | start_times = [] # type: List[int] |
| 282 | stop_times = [] # type: List[int] |
| 283 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 284 | for future in jfutures: |
| 285 | for (node_id, dev, sensor_name), ts in future.result().items(): |
| 286 | self.rstorage.put_ts(ts, job_path, node_id=node_id, dev=dev, sensor_name=sensor_name) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 287 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 288 | if len(ts.times) >= 2: |
| 289 | start_times.append(ts.times[0]) |
| 290 | stop_times.append(ts.times[-1]) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 291 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 292 | if len(start_times) > 0: |
| 293 | min_start_time = min(start_times) |
| 294 | max_start_time = max(start_times) |
| 295 | min_stop_time = min(stop_times) |
| 296 | max_stop_time = max(stop_times) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 297 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 298 | max_allowed_time_diff = int((min_stop_time - max_start_time) * self.max_rel_time_diff) |
| 299 | max_allowed_time_diff = max(max_allowed_time_diff, self.max_time_diff) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 300 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 301 | if min_start_time + self.max_time_diff < max_allowed_time_diff: |
| 302 | logger.warning("Too large difference in %s:%s start time - %s. " + |
| 303 | "Max recommended difference is %s", |
| 304 | self.name, job_config.summary, |
| 305 | max_start_time - min_start_time, self.max_time_diff) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 306 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 307 | if min_stop_time + self.max_time_diff < max_allowed_time_diff: |
| 308 | logger.warning("Too large difference in %s:%s stop time - %s. " + |
| 309 | "Max recommended difference is %s", |
| 310 | self.name, job_config.summary, |
| 311 | max_start_time - min_start_time, self.max_time_diff) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 312 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 313 | self.rstorage.put_job_config(job_config, job_path) |
| 314 | self.storage.sync() |
koder aka kdanilov | 7f59d56 | 2016-12-26 01:34:23 +0200 | [diff] [blame] | 315 | |
| 316 | if self.on_idle is not None: |
| 317 | self.on_idle() |
| 318 | |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 319 | @abc.abstractmethod |
| 320 | def config_node(self, node: IRPCNode) -> None: |
| 321 | pass |
| 322 | |
| 323 | @abc.abstractmethod |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 324 | def prepare_iteration(self, node: IRPCNode, iter_config: TestJobConfig) -> None: |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 325 | pass |
| 326 | |
| 327 | @abc.abstractmethod |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 328 | def run_iteration(self, node: IRPCNode, iter_config: TestJobConfig, stor_prefix: str) -> JobMetrics: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 329 | pass |
| 330 | |
| 331 | |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 332 | class TwoScriptTest(ThreadedTest, metaclass=abc.ABCMeta): |
| 333 | def __init__(self, *dt, **mp) -> None: |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 334 | ThreadedTest.__init__(self, *dt, **mp) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 335 | self.prerun_script = self.config.params['prerun_script'] |
| 336 | self.run_script = self.config.params['run_script'] |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 337 | self.prerun_tout = self.config.params.get('prerun_tout', 3600) |
| 338 | self.run_tout = self.config.params.get('run_tout', 3600) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 339 | self.iterations_configs = [None] |
Yulia Portnova | 7ddfa73 | 2015-02-24 17:32:58 +0200 | [diff] [blame] | 340 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 341 | def get_expected_runtime(self, iter_cfg: TestJobConfig) -> Optional[int]: |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 342 | return None |
Yulia Portnova | 7ddfa73 | 2015-02-24 17:32:58 +0200 | [diff] [blame] | 343 | |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 344 | def config_node(self, node: IRPCNode) -> None: |
| 345 | node.copy_file(self.run_script, self.join_remote(self.run_script)) |
| 346 | node.copy_file(self.prerun_script, self.join_remote(self.prerun_script)) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 347 | |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 348 | cmd = self.join_remote(self.prerun_script) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 349 | cmd += ' ' + self.config.params.get('prerun_opts', '') |
koder aka kdanilov | 3b4da8b | 2016-10-17 00:17:53 +0300 | [diff] [blame] | 350 | node.run(cmd, timeout=self.prerun_tout) |
Yulia Portnova | 7ddfa73 | 2015-02-24 17:32:58 +0200 | [diff] [blame] | 351 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 352 | def prepare_iteration(self, node: IRPCNode, iter_config: TestJobConfig) -> None: |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 353 | pass |
| 354 | |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 355 | def run_iteration(self, node: IRPCNode, iter_config: TestJobConfig, stor_prefix: str) -> JobMetrics: |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 356 | # TODO: have to store logs |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 357 | cmd = self.join_remote(self.run_script) |
koder aka kdanilov | bc2c898 | 2015-06-13 02:50:43 +0300 | [diff] [blame] | 358 | cmd += ' ' + self.config.params.get('run_opts', '') |
koder aka kdanilov | 23e6bdf | 2016-12-24 02:18:54 +0200 | [diff] [blame] | 359 | return self.parse_results(node.run(cmd, timeout=self.run_tout)) |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 360 | |
| 361 | @abc.abstractmethod |
koder aka kdanilov | f286517 | 2016-12-30 03:35:11 +0200 | [diff] [blame^] | 362 | def parse_results(self, data: str) -> JobMetrics: |
koder aka kdanilov | 7022706 | 2016-11-26 23:23:21 +0200 | [diff] [blame] | 363 | pass |
| 364 | |