Help: add -j -l -p option to help string
[lttng-tools.git] / tests / regression / run-report.py
1 #!/usr/bin/env python
2
3 import os, sys
4 import subprocess
5 import threading
6 import Queue
7 import time
8 import shlex
9
10 from signal import signal, SIGTERM, SIGINT, SIGPIPE, SIG_DFL
11
12 SESSIOND_BIN_NAME = "lttng-sessiond"
13 SESSIOND_BIN_PATH = "src/bin/lttng-sessiond/"
14 CONSUMERD_BIN_NAME = "lttng-consumerd"
15 CONSUMERD_BIN_PATH = "src/bin/lttng-consumerd/"
16 TESTDIR_PATH = ""
17
18 PRINT_BRACKET = "\033[1;34m[\033[1;33m+\033[1;34m]\033[00m"
19 PRINT_RED_BRACKET = "\033[1;31m[+]\033[00m"
20 PRINT_GREEN_BRACKET = "\033[1;32m[+]\033[00m"
21 PRINT_ARROW = "\033[1;32m-->\033[00m"
22
23 is_root = 1
24 no_stats = 0
25 stop_sampling = 1
26
27 top_cpu_legend = { 'us': "User CPU time", 'sy': "System CPU time",
28 'id': "Idle CPU time", 'ni': "Nice CPU time", 'wa': "iowait",
29 'hi': "Hardware IRQ", 'si': "Software Interrupts", 'st': "Steal Time", }
30
31 cpu_ret_q = Queue.Queue()
32 mem_ret_q = Queue.Queue()
33 test_ret_q = Queue.Queue()
34
35 global sdaemon_proc
36 global worker_proc
37
38 def cpu_create_usage_dict(top_line):
39 """
40 Return a dictionnary from a 'top' cpu line.
41 Ex: Cpu(s): 2.1%us, 1.2%sy, 0.0%ni, 96.2%id, 0.4%wa, 0.0%hi, 0.0%si, 0.0%st
42 """
43 top_dict = {'us': 0, 'sy': 0, 'ni': 0, 'id': 0, 'wa': 0, 'hi': 0, 'si': 0, 'st': 0}
44
45 # Split expression and remove first value which is "Cpu(s)"
46 top_line = top_line.replace(",","")
47 words = top_line.split()[1:]
48
49
50 for key in top_dict:
51 index = words.index(key)
52 # Add the value to the dictionnary
53 val = words[index-1]
54 top_dict[key] = float(val)
55
56 return top_dict
57
58 def cpu_average_usage(top_lines):
59 """
60 Return a dictionnary of 'top' CPU stats but averaging all values.
61 """
62 avg_dict = {'us': 0, 'sy': 0, 'ni': 0, 'id': 0, 'wa': 0, 'hi': 0, 'si': 0, 'st': 0}
63 # Average count
64 count = 0.0
65
66 for line in top_lines:
67 tmp_dict = cpu_create_usage_dict(line)
68 # Add value to avg dictionnary
69 for key in tmp_dict:
70 avg_dict[key] += tmp_dict[key]
71
72 count += 1.0
73
74 for key in avg_dict:
75 avg_dict[key] = avg_dict[key] / count
76
77 return (count, avg_dict)
78
79 def cpu_sample_usage(pid=None):
80 """
81 Sample CPU usage for num iterations.
82 If num is greater than 1, the average will be computed.
83 """
84 args = ["top", "-b", "-n", "1"]
85 if pid:
86 args.append("-p")
87 args.append(str(pid))
88
89 # Spawn top process
90 top = subprocess.Popen(args, stdout = subprocess.PIPE)
91
92 grep = subprocess.Popen(["grep", "Cpu"], stdin = top.stdout,
93 stdout = subprocess.PIPE)
94 top.stdout.close()
95
96 return grep.communicate()[0].strip("\n")
97
98 def mem_sample_usage(pid):
99 """
100 Sample memory usage using /proc and a pid
101 """
102 args = ["cat", "/proc/" + str(pid) + "/status"]
103
104 if not os.path.isfile(args[1]):
105 return -1
106
107 mem_proc = subprocess.Popen(args, stdout = subprocess.PIPE)
108
109 grep = subprocess.Popen(["grep", "^VmRSS"], stdin = mem_proc.stdout,
110 stdout = subprocess.PIPE)
111 mem_proc.stdout.close()
112
113 # Return virtual memory size in kilobytes (kB)
114 #ret = grep.communicate()[0].split()
115 ret = grep.communicate()[0].split()
116
117 if len(ret) > 1:
118 ret = ret[1]
119 else:
120 ret = 0
121
122 return int(ret)
123
124 class SamplingWorker(threading.Thread):
125 def __init__(self, s_type, worker = None, delay = 0.2, pid = 0):
126 threading.Thread.__init__ (self)
127 self.s_type = s_type
128 self.delay = delay
129 self.pid = pid
130 self.worker = worker
131
132 def run(self):
133 count = 1
134 lines = []
135
136 if self.s_type == "cpu":
137 while 1:
138 if self.worker == None:
139 cpu_line = cpu_sample_usage(self.pid)
140 lines.append(cpu_line)
141 break
142 elif self.worker.is_alive():
143 cpu_line = cpu_sample_usage(self.pid)
144 lines.append(cpu_line)
145 else:
146 break
147
148 # Delay sec per memory sampling
149 time.sleep(self.delay)
150
151 count, stats = cpu_average_usage(lines)
152 cpu_ret_q.put((count, stats))
153 # grep process has ended here
154
155 elif self.s_type == "mem":
156 count = 0
157 mem_stat = 0
158
159 while 1:
160 if self.worker == None:
161 cpu_line = cpu_sample_usage(self.pid)
162 lines.append(cpu_line)
163 break
164 elif self.worker.is_alive():
165 mem_stat += get_mem_usage(self.pid)
166 count += 1
167 else:
168 break
169
170 # Delay sec per memory sampling
171 time.sleep(self.delay)
172
173 mem_ret_q.put((count, mem_stat))
174
175 class TestWorker(threading.Thread):
176 def __init__(self, path, name, env):
177 threading.Thread.__init__(self)
178 self.path = path
179 self.name = name
180 self.env = env
181
182 def run(self):
183 bin_path_name = os.path.join(self.path, self.name)
184
185 test = subprocess.Popen([bin_path_name], env=self.env, preexec_fn = lambda: signal(SIGPIPE, SIG_DFL))
186 test.wait()
187
188 # Send ret value to main thread
189 test_ret_q.put(test.returncode)
190
191 def get_pid(procname):
192 """
193 Return pid of process name using 'pidof' command
194 """
195 pidof = subprocess.Popen(["pidof", procname], stdout = subprocess.PIPE)
196 pid = pidof.communicate()[0].split()
197
198 if pid == []:
199 return 0
200
201 return int(pid[0])
202
203 def spawn_session_daemon():
204 """
205 Exec the session daemon and return PID
206 """
207 global sdaemon_proc
208
209 pid = get_pid(SESSIOND_BIN_NAME)
210 if pid != 0:
211 os.kill(pid, SIGTERM)
212
213 bin_path = os.path.join(TESTDIR_PATH, "..", SESSIOND_BIN_PATH, SESSIOND_BIN_NAME)
214 consumer_path = os.path.join(TESTDIR_PATH, "..", CONSUMERD_BIN_PATH, CONSUMERD_BIN_NAME)
215
216 if not os.path.isfile(bin_path):
217 print "Error: No session daemon binary found. Compiled?"
218 return 0
219
220 try:
221 args = shlex.split("libtool execute " + bin_path
222 + " --consumerd32-path=" + consumer_path
223 + " --consumerd64-path=" + consumer_path)
224
225 sdaemon_proc = subprocess.Popen(args, shell = False, stderr = subprocess.PIPE)
226
227 except OSError, e:
228 print e
229 return 0
230
231 time.sleep(1)
232
233 return get_pid("lt-" + SESSIOND_BIN_NAME)
234
235 def start_test(name):
236 """
237 Spawn test and return exit code
238 """
239 tw = TestWorker(".", name)
240 tw.start()
241
242 return test_ret_q.get(True)
243
244 def print_cpu_stats(stats, count):
245 """
246 Pretty print on one line the CPU stats
247 """
248 sys.stdout.write(PRINT_ARROW + " Cpu [sampled %d time(s)]:\n " % (count))
249 for stat in stats:
250 sys.stdout.write(" %s: %.2f, " % (stat, stats[stat]))
251 print ""
252
253 def get_cpu_usage(delay=1, pid=0):
254 """
255 Spawn a worker thread to sample cpu usage.
256 """
257 sw = SamplingWorker("cpu", delay = delay, pid = pid)
258 sw.start()
259
260 return cpu_ret_q.get(True)
261
262 def get_mem_usage(pid):
263 """
264 Get memory usage for PID
265 """
266 return mem_sample_usage(pid)
267
268 def print_test_success(ret, expect):
269 """
270 Print if test has failed or pass according to the expected value.
271 """
272 if ret != expect:
273 print "\n" + PRINT_RED_BRACKET + \
274 " Failed: ret = %d (expected %d)" % (ret, expect)
275 return 1
276 else:
277 print "\n" + PRINT_BRACKET + \
278 " Passed: ret = %d (expected %d)" % (ret, expect)
279 return 0
280
281 def run_test(test):
282 """
283 Run test 'name' and output report of the test with stats.
284 """
285 global worker_proc
286 global sdaemon_proc
287 dem_pid = 0 # Session daemon pid
288
289 print PRINT_BRACKET + " %s" % (test['name'])
290 print PRINT_ARROW + " %s" % (test['desc'])
291 if no_stats:
292 print PRINT_ARROW + " Statistics will NOT be collected"
293 else:
294 print PRINT_ARROW + " Statistics of the session daemon will be collected"
295
296 if test['kern'] and not is_root:
297 print "Needs root for kernel tracing. Skipping"
298 return 0
299
300 if not os.path.isfile(test['bin']):
301 print "Unable to find test file '%s'. Skipping" % (test['bin'])
302 return 0
303
304 # Session daemon is controlled by the test
305 if test['daemon'] == "test":
306 print PRINT_ARROW + " Session daemon is controlled by the test"
307 env = os.environ
308 env['TEST_NO_SESSIOND'] = '0'
309 tw = TestWorker(".", test['bin'], env)
310 tw.start()
311 ret = test_ret_q.get(True)
312 print_test_success(ret, test['success'])
313 return 0
314 elif test['daemon'] == False:
315 print PRINT_ARROW + " No session daemon needed"
316 env = os.environ
317 env['TEST_NO_SESSIOND'] = '1'
318 tw = TestWorker(".", test['bin'], env)
319 tw.start()
320 ret = test_ret_q.get(True)
321 print_test_success(ret, test['success'])
322 return 0
323 else:
324 print PRINT_ARROW + " Session daemon needed"
325
326 dem_pid = spawn_session_daemon()
327 if dem_pid <= 0:
328 print "Unable to start %s. Stopping" % (SESSIOND_BIN_NAME)
329 print sdaemon_proc.communicate()[1]
330 return 0
331
332 print PRINT_BRACKET + " Session daemon spawned (pid: %d)\n" % (dem_pid)
333
334 if not no_stats:
335 mem_before = get_mem_usage(dem_pid)
336 print PRINT_BRACKET + " Stats *before* test:"
337 print PRINT_ARROW + " Mem (kB): %d" % (mem_before)
338 cpu_count, cpu_stats = get_cpu_usage(pid = dem_pid)
339 print_cpu_stats(cpu_stats, cpu_count)
340
341 # Sessiond was already spawned, do not let the test spawn
342 # an additional sessiond
343 env = os.environ
344 env['TEST_NO_SESSIOND'] = '1'
345
346 tw = TestWorker(".", test['bin'], env)
347 tw.start()
348
349 if not no_stats:
350 # Start CPU sampling for test
351 sw_cpu = SamplingWorker("cpu", worker = tw, pid = dem_pid)
352 sw_cpu.start()
353 sw_mem = SamplingWorker("mem", worker = tw, pid = dem_pid)
354 sw_mem.start()
355
356 ret = test_ret_q.get(True)
357
358 if not no_stats:
359 time.sleep(2)
360 # Compute memory average
361 mem_count, mem_during = mem_ret_q.get(True)
362 mem_during = float(mem_during) / float(mem_count)
363 cpu_count, cpu_stats = cpu_ret_q.get(True)
364
365 print "\n" + PRINT_BRACKET + " Stats *during* test:"
366 print PRINT_ARROW + " Mem (kB): %.0f [sampled %d time(s)]" % (mem_during, mem_count)
367 print_cpu_stats(cpu_stats, cpu_count)
368
369 mem_after = get_mem_usage(dem_pid)
370 print "\n" + PRINT_BRACKET + " Stats *after* test:"
371 print PRINT_ARROW + " Mem (kB): %d" % (mem_after)
372 cpu_count, cpu_stats = get_cpu_usage(pid = dem_pid)
373 print_cpu_stats(cpu_stats, cpu_count)
374
375 print "\n" + PRINT_BRACKET + " Memory usage differences:"
376 print PRINT_ARROW + " Diff during and before (kB): %d" % (mem_during - mem_before)
377 print PRINT_ARROW + " Diff during and after (kB): %d" % (mem_during - mem_after)
378 print PRINT_ARROW + " Diff before and after (kB): %d" % (mem_after - mem_before)
379
380 # Return value of 0 means that is passed else it failed
381 ret = print_test_success(ret, test['success'])
382
383 # Stop session daemon
384 if dem_pid > 0:
385 print PRINT_BRACKET + " Stopping session daemon (pid: %d)..." % (dem_pid)
386 try:
387 os.kill(dem_pid, SIGTERM)
388 # This call simply does not work... It seems python does not relay the signal
389 # to the child processes of sdaemon_proc.
390 # sdaemon_proc.terminate()
391 if ret != 0:
392 print sdaemon_proc.communicate()[1]
393 elif sdaemon_proc.returncode == None:
394 sdaemon_proc.communicate()
395 except OSError, e:
396 print e
397
398 # Make sure all thread are released
399 if not no_stats:
400 tw.join()
401 sw_cpu.join()
402 sw_mem.join()
403
404 return ret
405
406 def main():
407 for test in Tests:
408 if not test['enabled']:
409 continue
410
411 ret = run_test(test)
412 if ret != 0:
413 # Stop all tests, the last one failed
414 return
415 print ""
416
417 def cleanup(signo, stack):
418 """ Cleanup function """
419 sys.exit(0)
420
421 if __name__ == "__main__":
422 if not os.getuid() == 0:
423 is_root = 0
424 print "NOTICE: Not root. No kernel tracing will be tested\n"
425
426 if os.path.isfile("test_list.py"):
427 from test_list import Tests
428 else:
429 print "No test_list.py found. Stopping"
430 cleanup(0, 0)
431
432 TESTDIR_PATH = os.getcwd()
433
434 if len(sys.argv) > 1:
435 if sys.argv[1] == "--no-stats":
436 no_stats = 1
437
438 try:
439 signal(SIGTERM, cleanup)
440 signal(SIGINT, cleanup)
441 main()
442 cleanup(0, 0)
443 except KeyboardInterrupt:
444 cleanup(0, 0)
This page took 0.038737 seconds and 5 git commands to generate.