Commit | Line | Data |
---|---|---|
7c0a523d | 1 | #!/usr/bin/env python |
355f483d DG |
2 | |
3 | import os, sys | |
4 | import subprocess | |
5 | import threading | |
6 | import Queue | |
7 | import time | |
7c0a523d | 8 | import shlex |
355f483d | 9 | |
4ed5c01e | 10 | from signal import signal, SIGTERM, SIGINT, SIGPIPE, SIG_DFL |
355f483d DG |
11 | |
12 | SESSIOND_BIN_NAME = "lttng-sessiond" | |
7c0a523d CB |
13 | SESSIOND_BIN_PATH = "src/bin/lttng-sessiond/" |
14 | CONSUMERD_BIN_NAME = "lttng-consumerd" | |
15 | CONSUMERD_BIN_PATH = "src/bin/lttng-consumerd/" | |
355f483d DG |
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 | ||
1db84787 CB |
49 | |
50 | for key in top_dict: | |
51 | index = words.index(key) | |
355f483d | 52 | # Add the value to the dictionnary |
1db84787 CB |
53 | val = words[index-1] |
54 | top_dict[key] = float(val) | |
355f483d DG |
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 | ||
1db84787 | 92 | grep = subprocess.Popen(["grep", "Cpu"], stdin = top.stdout, |
355f483d DG |
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): | |
8c8d73ab | 176 | def __init__(self, path, name, env): |
355f483d DG |
177 | threading.Thread.__init__(self) |
178 | self.path = path | |
179 | self.name = name | |
8c8d73ab | 180 | self.env = env |
355f483d DG |
181 | |
182 | def run(self): | |
183 | bin_path_name = os.path.join(self.path, self.name) | |
184 | ||
8c8d73ab | 185 | test = subprocess.Popen([bin_path_name], env=self.env, preexec_fn = lambda: signal(SIGPIPE, SIG_DFL)) |
355f483d DG |
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) | |
7c0a523d | 214 | consumer_path = os.path.join(TESTDIR_PATH, "..", CONSUMERD_BIN_PATH, CONSUMERD_BIN_NAME) |
355f483d DG |
215 | |
216 | if not os.path.isfile(bin_path): | |
217 | print "Error: No session daemon binary found. Compiled?" | |
218 | return 0 | |
219 | ||
220 | try: | |
7c0a523d CB |
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 | ||
355f483d DG |
227 | except OSError, e: |
228 | print e | |
229 | return 0 | |
230 | ||
7c0a523d CB |
231 | time.sleep(1) |
232 | ||
233 | return get_pid("lt-" + SESSIOND_BIN_NAME) | |
355f483d DG |
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']): | |
e8913bd8 | 301 | print "Unable to find test file '%s'. Skipping" % (test['bin']) |
355f483d DG |
302 | return 0 |
303 | ||
8c8d73ab CB |
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: | |
355f483d | 315 | print PRINT_ARROW + " No session daemon needed" |
8c8d73ab CB |
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) | |
355f483d DG |
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 | ||
8c8d73ab CB |
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) | |
355f483d DG |
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) |