blob: 7004b8e7a99dddc59b5773415aeaac558b785482 [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
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030012from ..inode import INode
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020013from ..storage import Storage
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030014
15
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030016logger = logging.getLogger("wally")
koder aka kdanilov88407ff2015-05-26 15:35:57 +030017
18
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030019class TestConfig:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030020 """
21 this class describe test input configuration
koder aka kdanilov88407ff2015-05-26 15:35:57 +030022
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030023 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 kdanilov3b4da8b2016-10-17 00:17:53 +030030 def __init__(self,
31 test_type: str,
32 params: Dict[str, Any],
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020033 run_uuid: str,
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030034 nodes: List[INode],
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020035 storage: Storage,
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030036 remote_dir: str):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030037 self.test_type = test_type
38 self.params = params
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020039 self.run_uuid = run_uuid
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030040 self.nodes = nodes
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020041 self.storage = storage
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030042 self.remote_dir = remote_dir
koder aka kdanilov88407ff2015-05-26 15:35:57 +030043
44
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030045class TestResults:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030046 """
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 kdanilov3b4da8b2016-10-17 00:17:53 +030055 def __init__(self,
56 config: TestConfig,
57 results: Dict[str, Any],
58 raw_result: Any,
59 run_interval: Tuple[float, float]):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030060 self.config = config
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030061 self.params = config.params
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030062 self.results = results
63 self.raw_result = raw_result
64 self.run_interval = run_interval
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030065
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 kdanilove21d7472015-02-14 19:02:04 -080088
89
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030090# 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 kdanilovbc2c8982015-06-13 02:50:43 +0300103
104
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300105class MeasurementResults:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300106 def stat(self):
107 return data_property(self.data)
108
109 def __str__(self):
110 return 'TS([' + ", ".join(map(str, self.data)) + '])'
111
112
113class SimpleVals(MeasurementResults):
114 """
115 data:[float] - list of values
116 """
117 def __init__(self, data):
118 self.data = data
119
120
121class TimeSeriesValue(MeasurementResults):
122 """
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300123 data:[(float, float, float)] - list of (start_time, lenght, average_value_for_interval)
124 odata: original values
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300125 """
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300126 def __init__(self, data: List[Tuple[float, float, float]]):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300127 assert len(data) > 0
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300128 self.odata = data[:]
129 self.data = []
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300130
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300131 cstart = 0
132 for nstart, nval in data:
133 self.data.append((cstart, nstart - cstart, nval))
134 cstart = nstart
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300135
136 @property
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300137 def values(self) -> List[float]:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300138 return [val[2] for val in self.data]
139
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300140 def average_interval(self) -> float:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300141 return float(sum([val[1] for val in self.data])) / len(self.data)
142
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300143 def skip(self, seconds) -> 'TimeSeriesValue':
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300144 nres = []
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300145 for start, ln, val in self.data:
146 nstart = start + ln - seconds
147 if nstart > 0:
148 nres.append([nstart, val])
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300149 return self.__class__(nres)
150
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300151 def derived(self, tdelta) -> 'TimeSeriesValue':
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300152 end = self.data[-1][0] + self.data[-1][1]
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300153 tdelta = float(tdelta)
154
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300155 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 kdanilovbc2c8982015-06-13 02:50:43 +0300162 for start, lenght, val in self.data:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300163 start_idx = int(start / tdelta)
164 end_idx = int((start + lenght) / tdelta)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300165
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300166 for idx in range(start_idx, end_idx + 1):
167 rstart = tdelta * idx
168 rend = tdelta * (idx + 1)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300169
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300170 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 kdanilovbc2c8982015-06-13 02:50:43 +0300176
177 return self.__class__(res)
178
179
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300180class PerfTest:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300181 """
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 kdanilove2de58c2015-04-24 22:59:36 +0300188 self.stop_requested = False
189
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300190 def request_stop(self) -> None:
koder aka kdanilove2de58c2015-04-24 22:59:36 +0300191 self.stop_requested = True
koder aka kdanilov2066daf2015-04-23 21:05:41 +0300192
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300193 def join_remote(self, path: str) -> str:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300194 return os.path.join(self.config.remote_dir, path)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300195
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300196 @classmethod
197 @abc.abstractmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300198 def load(cls, path: str):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300199 pass
200
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800201 @abc.abstractmethod
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200202 def run(self):
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800203 pass
204
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300205 @abc.abstractmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300206 def format_for_console(cls, data: Any) -> str:
koder aka kdanilovec1b9732015-04-23 20:43:29 +0300207 pass
208
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800209
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300210class ThreadedTest(PerfTest):
211 """
212 Base class for tests, which spawn separated thread for each node
213 """
214
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300215 def run(self) -> List[TestResults]:
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200216 barrier = Barrier(len(self.config.nodes))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300217 th_test_func = functools.partial(self.th_test_func, barrier)
218
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200219 with ThreadPoolExecutor(len(self.config.nodes)) as pool:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300220 return list(pool.map(th_test_func, self.config.nodes))
221
222 @abc.abstractmethod
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300223 def do_test(self, node: INode) -> TestResults:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300224 pass
225
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300226 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 kdanilovbc2c8982015-06-13 02:50:43 +0300230 self.pre_run(node)
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300231
232 # wait till all thread became ready
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300233 barrier.wait()
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300234
235 logger.debug("Run test on {}".format(node))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300236 try:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300237 return self.do_test(node)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300238 except Exception as exc:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300239 msg = "In test {} for {}".format(test_name, node)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300240 logger.exception(msg)
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300241 raise StopTestError(msg) from exc
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300242
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300243 def pre_run(self, node: INode) -> None:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300244 pass
245
246
247class TwoScriptTest(ThreadedTest):
248 def __init__(self, *dt, **mp):
249 ThreadedTest.__init__(self, *dt, **mp)
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200250 self.remote_dir = '/tmp'
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300251 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 Portnova7ddfa732015-02-24 17:32:58 +0200256
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300257 def get_remote_for_script(self, script: str) -> str:
258 return os.path.join(self.remote_dir, os.path.basename(script))
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200259
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300260 def pre_run(self, node: INode) -> None:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300261 copy_paths(node.connection,
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300262 {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 kdanilovbc2c8982015-06-13 02:50:43 +0300264
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200265 cmd = self.get_remote_for_script(self.prerun_script)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300266 cmd += ' ' + self.config.params.get('prerun_opts', '')
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300267 node.run(cmd, timeout=self.prerun_tout)
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200268
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300269 def do_test(self, node: INode) -> TestResults:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300270 cmd = self.get_remote_for_script(self.run_script)
271 cmd += ' ' + self.config.params.get('run_opts', '')
272 t1 = time.time()
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300273 res = node.run(cmd, timeout=self.run_tout)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300274 t2 = time.time()
275 return TestResults(self.config, None, res, (t1, t2))