blob: 0529c1fb99701d3b8b62e57e4298806e8ee55493 [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 kdanilov652cd802015-04-13 12:21:07 +03006
koder aka kdanilovbc2c8982015-06-13 02:50:43 +03007from concurrent.futures import ThreadPoolExecutor
koder aka kdanilov4643fd62015-02-10 16:20:13 -08008
koder aka kdanilovbc2c8982015-06-13 02:50:43 +03009from wally.utils import Barrier, StopTestError
10from wally.statistic import data_property
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030011from wally.ssh_utils import run_over_ssh, copy_paths
12
13
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030014logger = logging.getLogger("wally")
koder aka kdanilov88407ff2015-05-26 15:35:57 +030015
16
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030017class TestConfig(object):
18 """
19 this class describe test input configuration
koder aka kdanilov88407ff2015-05-26 15:35:57 +030020
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030021 test_type:str - test type name
22 params:{str:Any} - parameters from yaml file for this test
23 test_uuid:str - UUID to be used to create filenames and Co
24 log_directory:str - local directory to store results
25 nodes:[Node] - node to run tests on
26 remote_dir:str - directory on nodes to be used for local files
27 """
28 def __init__(self, test_type, params, test_uuid, nodes,
29 log_directory, remote_dir):
30 self.test_type = test_type
31 self.params = params
32 self.test_uuid = test_uuid
33 self.log_directory = log_directory
34 self.nodes = nodes
35 self.remote_dir = remote_dir
koder aka kdanilov88407ff2015-05-26 15:35:57 +030036
37
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030038class TestResults(object):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030039 """
40 this class describe test results
41
42 config:TestConfig - test config object
43 params:dict - parameters from yaml file for this test
44 results:{str:MeasurementMesh} - test results object
45 raw_result:Any - opaque object to store raw results
46 run_interval:(float, float) - test tun time, used for sensors
47 """
48 def __init__(self, config, results, raw_result, run_interval):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030049 self.config = config
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030050 self.params = config.params
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030051 self.results = results
52 self.raw_result = raw_result
53 self.run_interval = run_interval
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030054
55 def __str__(self):
56 res = "{0}({1}):\n results:\n".format(
57 self.__class__.__name__,
58 self.summary())
59
60 for name, val in self.results.items():
61 res += " {0}={1}\n".format(name, val)
62
63 res += " params:\n"
64
65 for name, val in self.params.items():
66 res += " {0}={1}\n".format(name, val)
67
68 return res
69
70 @abc.abstractmethod
71 def summary(self):
72 pass
73
74 @abc.abstractmethod
75 def get_yamable(self):
76 pass
koder aka kdanilove21d7472015-02-14 19:02:04 -080077
78
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030079class MeasurementMatrix(object):
80 """
81 data:[[MeasurementResult]] - VM_COUNT x TH_COUNT matrix of MeasurementResult
82 """
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +030083 def __init__(self, data, connections_ids):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030084 self.data = data
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +030085 self.connections_ids = connections_ids
koder aka kdanilovbc2c8982015-06-13 02:50:43 +030086
87 def per_vm(self):
88 return self.data
89
90 def per_th(self):
91 return sum(self.data, [])
92
93
94class MeasurementResults(object):
95 def stat(self):
96 return data_property(self.data)
97
98 def __str__(self):
99 return 'TS([' + ", ".join(map(str, self.data)) + '])'
100
101
102class SimpleVals(MeasurementResults):
103 """
104 data:[float] - list of values
105 """
106 def __init__(self, data):
107 self.data = data
108
109
110class TimeSeriesValue(MeasurementResults):
111 """
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300112 data:[(float, float, float)] - list of (start_time, lenght, average_value_for_interval)
113 odata: original values
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300114 """
115 def __init__(self, data):
116 assert len(data) > 0
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300117 self.odata = data[:]
118 self.data = []
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300119
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300120 cstart = 0
121 for nstart, nval in data:
122 self.data.append((cstart, nstart - cstart, nval))
123 cstart = nstart
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300124
125 @property
126 def values(self):
127 return [val[2] for val in self.data]
128
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300129 def average_interval(self):
130 return float(sum([val[1] for val in self.data])) / len(self.data)
131
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300132 def skip(self, seconds):
133 nres = []
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300134 for start, ln, val in self.data:
135 nstart = start + ln - seconds
136 if nstart > 0:
137 nres.append([nstart, val])
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300138 return self.__class__(nres)
139
140 def derived(self, tdelta):
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300141 end = self.data[-1][0] + self.data[-1][1]
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300142 tdelta = float(tdelta)
143
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300144 ln = end / tdelta
145
146 if ln - int(ln) > 0:
147 ln += 1
148
149 res = [[tdelta * i, 0.0] for i in range(int(ln))]
150
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300151 for start, lenght, val in self.data:
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300152 start_idx = int(start / tdelta)
153 end_idx = int((start + lenght) / tdelta)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300154
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300155 for idx in range(start_idx, end_idx + 1):
156 rstart = tdelta * idx
157 rend = tdelta * (idx + 1)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300158
koder aka kdanilovbb6d6cd2015-06-20 02:55:07 +0300159 intersection_ln = min(rend, start + lenght) - max(start, rstart)
160 if intersection_ln > 0:
161 try:
162 res[idx][1] += val * intersection_ln / tdelta
163 except IndexError:
164 raise
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300165
166 return self.__class__(res)
167
168
169class PerfTest(object):
170 """
171 Very base class for tests
172 config:TestConfig - test configuration
173 stop_requested:bool - stop for test requested
174 """
175 def __init__(self, config):
176 self.config = config
koder aka kdanilove2de58c2015-04-24 22:59:36 +0300177 self.stop_requested = False
178
179 def request_stop(self):
180 self.stop_requested = True
koder aka kdanilov2066daf2015-04-23 21:05:41 +0300181
182 def join_remote(self, path):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300183 return os.path.join(self.config.remote_dir, path)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300184
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300185 @classmethod
186 @abc.abstractmethod
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300187 def load(cls, path):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300188 pass
189
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800190 @abc.abstractmethod
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300191 def run(self):
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800192 pass
193
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300194 @abc.abstractmethod
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300195 def format_for_console(cls, data):
koder aka kdanilovec1b9732015-04-23 20:43:29 +0300196 pass
197
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800198
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300199def run_on_node(node):
200 def closure(*args, **kwargs):
201 return run_over_ssh(node.connection,
202 *args,
203 node=node.get_conn_id(),
204 **kwargs)
205 return closure
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200206
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300207
208class ThreadedTest(PerfTest):
209 """
210 Base class for tests, which spawn separated thread for each node
211 """
212
213 def run(self):
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200214 barrier = Barrier(len(self.config.nodes))
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300215 th_test_func = functools.partial(self.th_test_func, barrier)
216
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200217 with ThreadPoolExecutor(len(self.config.nodes)) as pool:
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300218 return list(pool.map(th_test_func, self.config.nodes))
219
220 @abc.abstractmethod
221 def do_test(self, node):
222 pass
223
224 def th_test_func(self, barrier, node):
225 logger.debug("Starting {0} test on {1} node".format(self.__class__.__name__,
226 node.conn_url))
227
228 logger.debug("Run preparation for {0}".format(node.get_conn_id()))
229 self.pre_run(node)
230 barrier.wait()
231 try:
232 logger.debug("Run test for {0}".format(node.get_conn_id()))
233 return self.do_test(node)
234 except StopTestError as exc:
235 pass
236 except Exception as exc:
237 msg = "In test {0} for node {1}".format(self, node.get_conn_id())
238 logger.exception(msg)
239 exc = StopTestError(msg, exc)
240
241 try:
242 self.cleanup()
243 except StopTestError as exc1:
244 if exc is None:
245 exc = exc1
246 except Exception as exc1:
247 if exc is None:
248 msg = "Duringf cleanup - in test {0} for node {1}".format(self, node)
249 logger.exception(msg)
250 exc = StopTestError(msg, exc)
251
252 if exc is not None:
253 raise exc
254
255 def pre_run(self, node):
256 pass
257
258 def cleanup(self, node):
259 pass
260
261
262class TwoScriptTest(ThreadedTest):
263 def __init__(self, *dt, **mp):
264 ThreadedTest.__init__(self, *dt, **mp)
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200265 self.remote_dir = '/tmp'
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300266 self.prerun_script = self.config.params['prerun_script']
267 self.run_script = self.config.params['run_script']
268
269 self.prerun_tout = self.config.params.get('prerun_tout', 3600)
270 self.run_tout = self.config.params.get('run_tout', 3600)
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200271
272 def get_remote_for_script(self, script):
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200273 return os.path.join(self.remote_dir,
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300274 os.path.basename(script))
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200275
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300276 def pre_run(self, node):
277 copy_paths(node.connection,
278 {
279 self.run_script: self.get_remote_for_script(self.run_script),
280 self.prerun_script: self.get_remote_for_script(self.prerun_script),
281 })
282
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200283 cmd = self.get_remote_for_script(self.prerun_script)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300284 cmd += ' ' + self.config.params.get('prerun_opts', '')
285 run_on_node(node)(cmd, timeout=self.prerun_tout)
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200286
Yulia Portnovab0c977c2015-12-11 19:23:28 +0200287 def do_test(self, node):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300288 cmd = self.get_remote_for_script(self.run_script)
289 cmd += ' ' + self.config.params.get('run_opts', '')
290 t1 = time.time()
291 res = run_on_node(node)(cmd, timeout=self.run_tout)
292 t2 = time.time()
293 return TestResults(self.config, None, res, (t1, t2))