blob: 00492c97aa40f4e05740862bb0a5e13d7931b6dc [file] [log] [blame]
koder aka kdanilov4643fd62015-02-10 16:20:13 -08001import abc
koder aka kdanilovbc2c8982015-06-13 02:50:43 +03002import time
3import logging
koder aka kdanilov4643fd62015-02-10 16:20:13 -08004import os.path
koder aka kdanilov88407ff2015-05-26 15:35:57 +03005import functools
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +03006from typing import Dict, Any, List, Tuple
koder aka kdanilov652cd802015-04-13 12:21:07 +03007
koder aka kdanilovbc2c8982015-06-13 02:50:43 +03008from concurrent.futures import ThreadPoolExecutor
koder aka kdanilov4643fd62015-02-10 16:20:13 -08009
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030010from ..utils import Barrier, StopTestError
11from ..statistic import data_property
12from ..ssh_utils import copy_paths
13from ..inode import INode
14
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030015
16
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030017logger = logging.getLogger("wally")
koder aka kdanilov88407ff2015-05-26 15:35:57 +030018
19
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030020class TestConfig:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030021 """
22 this class describe test input configuration
koder aka kdanilov88407ff2015-05-26 15:35:57 +030023
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030024 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 kdanilov3b4da8b2016-10-17 00:17:53 +030031 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 kdanilovbc2c8982015-06-13 02:50:43 +030038 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 kdanilov88407ff2015-05-26 15:35:57 +030044
45
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030046class TestResults:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030047 """
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 kdanilov3b4da8b2016-10-17 00:17:53 +030056 def __init__(self,
57 config: TestConfig,
58 results: Dict[str, Any],
59 raw_result: Any,
60 run_interval: Tuple[float, float]):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030061 self.config = config
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030062 self.params = config.params
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030063 self.results = results
64 self.raw_result = raw_result
65 self.run_interval = run_interval
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030066
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 kdanilove21d7472015-02-14 19:02:04 -080089
90
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030091# 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 kdanilovbc2c8982015-06-13 02:50:43 +0300104
105
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300106class MeasurementResults:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300107 def stat(self):
108 return data_property(self.data)
109
110 def __str__(self):
111 return 'TS([' + ", ".join(map(str, self.data)) + '])'
112
113
114class SimpleVals(MeasurementResults):
115 """
116 data:[float] - list of values
117 """
118 def __init__(self, data):
119 self.data = data
120
121
122class TimeSeriesValue(MeasurementResults):
123 """
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300124 data:[(float, float, float)] - list of (start_time, lenght, average_value_for_interval)
125 odata: original values
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300126 """
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300127 def __init__(self, data: List[Tuple[float, float, float]]):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300128 assert len(data) > 0
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300129 self.odata = data[:]
130 self.data = []
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300131
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300132 cstart = 0
133 for nstart, nval in data:
134 self.data.append((cstart, nstart - cstart, nval))
135 cstart = nstart
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300136
137 @property
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300138 def values(self) -> List[float]:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300139 return [val[2] for val in self.data]
140
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300141 def average_interval(self) -> float:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300142 return float(sum([val[1] for val in self.data])) / len(self.data)
143
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300144 def skip(self, seconds) -> 'TimeSeriesValue':
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300145 nres = []
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300146 for start, ln, val in self.data:
147 nstart = start + ln - seconds
148 if nstart > 0:
149 nres.append([nstart, val])
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300150 return self.__class__(nres)
151
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300152 def derived(self, tdelta) -> 'TimeSeriesValue':
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300153 end = self.data[-1][0] + self.data[-1][1]
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300154 tdelta = float(tdelta)
155
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300156 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 kdanilovbc2c8982015-06-13 02:50:43 +0300163 for start, lenght, val in self.data:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300164 start_idx = int(start / tdelta)
165 end_idx = int((start + lenght) / tdelta)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300166
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300167 for idx in range(start_idx, end_idx + 1):
168 rstart = tdelta * idx
169 rend = tdelta * (idx + 1)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300170
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300171 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 kdanilovbc2c8982015-06-13 02:50:43 +0300177
178 return self.__class__(res)
179
180
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300181class PerfTest:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300182 """
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 kdanilove2de58c2015-04-24 22:59:36 +0300189 self.stop_requested = False
190
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300191 def request_stop(self) -> None:
koder aka kdanilove2de58c2015-04-24 22:59:36 +0300192 self.stop_requested = True
koder aka kdanilov2066daf2015-04-23 21:05:41 +0300193
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300194 def join_remote(self, path: str) -> str:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300195 return os.path.join(self.config.remote_dir, path)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300196
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300197 @classmethod
198 @abc.abstractmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300199 def load(cls, path: str):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300200 pass
201
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800202 @abc.abstractmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300203 def run(self) -> List[TestResults]:
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800204 pass
205
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300206 @abc.abstractmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300207 def format_for_console(cls, data: Any) -> str:
koder aka kdanilovec1b9732015-04-23 20:43:29 +0300208 pass
209
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800210
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300211class ThreadedTest(PerfTest):
212 """
213 Base class for tests, which spawn separated thread for each node
214 """
215
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300216 def run(self) -> List[TestResults]:
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200217 barrier = Barrier(len(self.config.nodes))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300218 th_test_func = functools.partial(self.th_test_func, barrier)
219
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200220 with ThreadPoolExecutor(len(self.config.nodes)) as pool:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300221 return list(pool.map(th_test_func, self.config.nodes))
222
223 @abc.abstractmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300224 def do_test(self, node: INode) -> TestResults:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300225 pass
226
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300227 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 kdanilovbc2c8982015-06-13 02:50:43 +0300231 self.pre_run(node)
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300232
233 # wait till all thread became ready
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300234 barrier.wait()
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300235
236 logger.debug("Run test on {}".format(node))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300237 try:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300238 return self.do_test(node)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300239 except Exception as exc:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300240 msg = "In test {} for {}".format(test_name, node)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300241 logger.exception(msg)
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300242 raise StopTestError(msg) from exc
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300243
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300244 def pre_run(self, node: INode) -> None:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300245 pass
246
247
248class TwoScriptTest(ThreadedTest):
249 def __init__(self, *dt, **mp):
250 ThreadedTest.__init__(self, *dt, **mp)
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200251 self.remote_dir = '/tmp'
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300252 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 Portnova7ddfa732015-02-24 17:32:58 +0200257
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300258 def get_remote_for_script(self, script: str) -> str:
259 return os.path.join(self.remote_dir, os.path.basename(script))
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200260
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300261 def pre_run(self, node: INode) -> None:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300262 copy_paths(node.connection,
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300263 {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 kdanilovbc2c8982015-06-13 02:50:43 +0300265
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200266 cmd = self.get_remote_for_script(self.prerun_script)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300267 cmd += ' ' + self.config.params.get('prerun_opts', '')
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300268 node.run(cmd, timeout=self.prerun_tout)
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200269
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300270 def do_test(self, node: INode) -> TestResults:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300271 cmd = self.get_remote_for_script(self.run_script)
272 cmd += ' ' + self.config.params.get('run_opts', '')
273 t1 = time.time()
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300274 res = node.run(cmd, timeout=self.run_tout)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300275 t2 = time.time()
276 return TestResults(self.config, None, res, (t1, t2))