blob: 7564722b250f29f13ba4676cf55cc368806d34bd [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 """
83 def __init__(self, data):
84 self.data = data
85
86 def per_vm(self):
87 return self.data
88
89 def per_th(self):
90 return sum(self.data, [])
91
92
93class MeasurementResults(object):
94 def stat(self):
95 return data_property(self.data)
96
97 def __str__(self):
98 return 'TS([' + ", ".join(map(str, self.data)) + '])'
99
100
101class SimpleVals(MeasurementResults):
102 """
103 data:[float] - list of values
104 """
105 def __init__(self, data):
106 self.data = data
107
108
109class TimeSeriesValue(MeasurementResults):
110 """
111 values:[(float, float, float)] - list of (start_time, lenght, average_value_for_interval)
112 """
113 def __init__(self, data):
114 assert len(data) > 0
115 data = [(0, 0)] + data
116
117 self.values = []
118 for (cstart, cval), (nstart, nval) in zip(data[:-1], data[1:]):
119 self.values.append((cstart, nstart - cstart, nval))
120
121 @property
122 def values(self):
123 return [val[2] for val in self.data]
124
125 def skip(self, seconds):
126 nres = []
127 for start, ln, val in enumerate(self.data):
128 if start + ln < seconds:
129 continue
130 elif start > seconds:
131 nres.append([start + ln - seconds, val])
132 else:
133 nres.append([0, val])
134 return self.__class__(nres)
135
136 def derived(self, tdelta):
137 end = tdelta
138 res = [[end, 0.0]]
139 tdelta = float(tdelta)
140
141 for start, lenght, val in self.data:
142 if start < end:
143 ln = min(end, start + lenght) - start
144 res[-1][1] += val * ln / tdelta
145
146 if end <= start + lenght:
147 end += tdelta
148 res.append([end, 0.0])
149 while end < start + lenght:
150 res[-1][1] = val
151 res.append([end, 0.0])
152 end += tdelta
153
154 if res[-1][1] < 1:
155 res = res[:-1]
156
157 return self.__class__(res)
158
159
160class PerfTest(object):
161 """
162 Very base class for tests
163 config:TestConfig - test configuration
164 stop_requested:bool - stop for test requested
165 """
166 def __init__(self, config):
167 self.config = config
koder aka kdanilove2de58c2015-04-24 22:59:36 +0300168 self.stop_requested = False
169
170 def request_stop(self):
171 self.stop_requested = True
koder aka kdanilov2066daf2015-04-23 21:05:41 +0300172
173 def join_remote(self, path):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300174 return os.path.join(self.config.remote_dir, path)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300175
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300176 @classmethod
177 @abc.abstractmethod
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300178 def load(cls, path):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300179 pass
180
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800181 @abc.abstractmethod
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300182 def run(self):
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800183 pass
184
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300185 @abc.abstractmethod
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300186 def format_for_console(cls, data):
koder aka kdanilovec1b9732015-04-23 20:43:29 +0300187 pass
188
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800189
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300190def run_on_node(node):
191 def closure(*args, **kwargs):
192 return run_over_ssh(node.connection,
193 *args,
194 node=node.get_conn_id(),
195 **kwargs)
196 return closure
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200197
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300198
199class ThreadedTest(PerfTest):
200 """
201 Base class for tests, which spawn separated thread for each node
202 """
203
204 def run(self):
205 barrier = Barrier(len(self.nodes))
206 th_test_func = functools.partial(self.th_test_func, barrier)
207
208 with ThreadPoolExecutor(len(self.nodes)) as pool:
209 return list(pool.map(th_test_func, self.config.nodes))
210
211 @abc.abstractmethod
212 def do_test(self, node):
213 pass
214
215 def th_test_func(self, barrier, node):
216 logger.debug("Starting {0} test on {1} node".format(self.__class__.__name__,
217 node.conn_url))
218
219 logger.debug("Run preparation for {0}".format(node.get_conn_id()))
220 self.pre_run(node)
221 barrier.wait()
222 try:
223 logger.debug("Run test for {0}".format(node.get_conn_id()))
224 return self.do_test(node)
225 except StopTestError as exc:
226 pass
227 except Exception as exc:
228 msg = "In test {0} for node {1}".format(self, node.get_conn_id())
229 logger.exception(msg)
230 exc = StopTestError(msg, exc)
231
232 try:
233 self.cleanup()
234 except StopTestError as exc1:
235 if exc is None:
236 exc = exc1
237 except Exception as exc1:
238 if exc is None:
239 msg = "Duringf cleanup - in test {0} for node {1}".format(self, node)
240 logger.exception(msg)
241 exc = StopTestError(msg, exc)
242
243 if exc is not None:
244 raise exc
245
246 def pre_run(self, node):
247 pass
248
249 def cleanup(self, node):
250 pass
251
252
253class TwoScriptTest(ThreadedTest):
254 def __init__(self, *dt, **mp):
255 ThreadedTest.__init__(self, *dt, **mp)
256
257 self.prerun_script = self.config.params['prerun_script']
258 self.run_script = self.config.params['run_script']
259
260 self.prerun_tout = self.config.params.get('prerun_tout', 3600)
261 self.run_tout = self.config.params.get('run_tout', 3600)
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200262
263 def get_remote_for_script(self, script):
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300264 return os.path.join(self.options.remote_dir,
265 os.path.basename(script))
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200266
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300267 def pre_run(self, node):
268 copy_paths(node.connection,
269 {
270 self.run_script: self.get_remote_for_script(self.run_script),
271 self.prerun_script: self.get_remote_for_script(self.prerun_script),
272 })
273
Yulia Portnovab1a15072015-05-06 14:59:25 +0300274 cmd = self.get_remote_for_script(self.pre_run_script)
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300275 cmd += ' ' + self.config.params.get('prerun_opts', '')
276 run_on_node(node)(cmd, timeout=self.prerun_tout)
Yulia Portnova7ddfa732015-02-24 17:32:58 +0200277
koder aka kdanilovbc2c8982015-06-13 02:50:43 +0300278 def run(self, node):
279 cmd = self.get_remote_for_script(self.run_script)
280 cmd += ' ' + self.config.params.get('run_opts', '')
281 t1 = time.time()
282 res = run_on_node(node)(cmd, timeout=self.run_tout)
283 t2 = time.time()
284 return TestResults(self.config, None, res, (t1, t2))