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): | |
176 | def __init__(self, path, name): | |
177 | threading.Thread.__init__(self) | |
178 | self.path = path | |
179 | self.name = name | |
180 | ||
181 | def run(self): | |
182 | bin_path_name = os.path.join(self.path, self.name) | |
183 | ||
184 | env = os.environ | |
185 | env['TEST_NO_SESSIOND'] = '1' | |
186 | ||
4ed5c01e | 187 | test = subprocess.Popen([bin_path_name], env=env, preexec_fn = lambda: signal(SIGPIPE, SIG_DFL)) |
355f483d DG |
188 | test.wait() |
189 | ||
190 | # Send ret value to main thread | |
191 | test_ret_q.put(test.returncode) | |
192 | ||
193 | def get_pid(procname): | |
194 | """ | |
195 | Return pid of process name using 'pidof' command | |
196 | """ | |
197 | pidof = subprocess.Popen(["pidof", procname], stdout = subprocess.PIPE) | |
198 | pid = pidof.communicate()[0].split() | |
199 | ||
200 | if pid == []: | |
201 | return 0 | |
202 | ||
203 | return int(pid[0]) | |
204 | ||
205 | def spawn_session_daemon(): | |
206 | """ | |
207 | Exec the session daemon and return PID | |
208 | """ | |
209 | global sdaemon_proc | |
210 | ||
211 | pid = get_pid(SESSIOND_BIN_NAME) | |
212 | if pid != 0: | |
213 | os.kill(pid, SIGTERM) | |
214 | ||
215 | bin_path = os.path.join(TESTDIR_PATH, "..", SESSIOND_BIN_PATH, SESSIOND_BIN_NAME) | |
7c0a523d | 216 | consumer_path = os.path.join(TESTDIR_PATH, "..", CONSUMERD_BIN_PATH, CONSUMERD_BIN_NAME) |
355f483d DG |
217 | |
218 | if not os.path.isfile(bin_path): | |
219 | print "Error: No session daemon binary found. Compiled?" | |
220 | return 0 | |
221 | ||
222 | try: | |
7c0a523d CB |
223 | args = shlex.split("libtool execute " + bin_path |
224 | + " --consumerd32-path=" + consumer_path | |
225 | + " --consumerd64-path=" + consumer_path) | |
226 | ||
227 | sdaemon_proc = subprocess.Popen(args, shell = False, stderr = subprocess.PIPE) | |
228 | ||
355f483d DG |
229 | except OSError, e: |
230 | print e | |
231 | return 0 | |
232 | ||
7c0a523d CB |
233 | time.sleep(1) |
234 | ||
235 | return get_pid("lt-" + SESSIOND_BIN_NAME) | |
355f483d DG |
236 | |
237 | def start_test(name): | |
238 | """ | |
239 | Spawn test and return exit code | |
240 | """ | |
241 | tw = TestWorker(".", name) | |
242 | tw.start() | |
243 | ||
244 | return test_ret_q.get(True) | |
245 | ||
246 | def print_cpu_stats(stats, count): | |
247 | """ | |
248 | Pretty print on one line the CPU stats | |
249 | """ | |
250 | sys.stdout.write(PRINT_ARROW + " Cpu [sampled %d time(s)]:\n " % (count)) | |
251 | for stat in stats: | |
252 | sys.stdout.write(" %s: %.2f, " % (stat, stats[stat])) | |
253 | print "" | |
254 | ||
255 | def get_cpu_usage(delay=1, pid=0): | |
256 | """ | |
257 | Spawn a worker thread to sample cpu usage. | |
258 | """ | |
259 | sw = SamplingWorker("cpu", delay = delay, pid = pid) | |
260 | sw.start() | |
261 | ||
262 | return cpu_ret_q.get(True) | |
263 | ||
264 | def get_mem_usage(pid): | |
265 | """ | |
266 | Get memory usage for PID | |
267 | """ | |
268 | return mem_sample_usage(pid) | |
269 | ||
270 | def print_test_success(ret, expect): | |
271 | """ | |
272 | Print if test has failed or pass according to the expected value. | |
273 | """ | |
274 | if ret != expect: | |
275 | print "\n" + PRINT_RED_BRACKET + \ | |
276 | " Failed: ret = %d (expected %d)" % (ret, expect) | |
277 | return 1 | |
278 | else: | |
279 | print "\n" + PRINT_BRACKET + \ | |
280 | " Passed: ret = %d (expected %d)" % (ret, expect) | |
281 | return 0 | |
282 | ||
283 | def run_test(test): | |
284 | """ | |
285 | Run test 'name' and output report of the test with stats. | |
286 | """ | |
287 | global worker_proc | |
288 | global sdaemon_proc | |
289 | dem_pid = 0 # Session daemon pid | |
290 | ||
291 | print PRINT_BRACKET + " %s" % (test['name']) | |
292 | print PRINT_ARROW + " %s" % (test['desc']) | |
293 | if no_stats: | |
294 | print PRINT_ARROW + " Statistics will NOT be collected" | |
295 | else: | |
296 | print PRINT_ARROW + " Statistics of the session daemon will be collected" | |
297 | ||
298 | if test['kern'] and not is_root: | |
299 | print "Needs root for kernel tracing. Skipping" | |
300 | return 0 | |
301 | ||
302 | if not os.path.isfile(test['bin']): | |
e8913bd8 | 303 | print "Unable to find test file '%s'. Skipping" % (test['bin']) |
355f483d DG |
304 | return 0 |
305 | ||
306 | # No session daemon needed | |
307 | if not test['daemon']: | |
308 | print PRINT_ARROW + " No session daemon needed" | |
309 | ret = start_test(test['bin']) | |
310 | print_test_success(ret, test['success']) | |
311 | return 0 | |
312 | else: | |
313 | print PRINT_ARROW + " Session daemon needed" | |
314 | ||
315 | dem_pid = spawn_session_daemon() | |
316 | if dem_pid <= 0: | |
317 | print "Unable to start %s. Stopping" % (SESSIOND_BIN_NAME) | |
318 | print sdaemon_proc.communicate()[1] | |
319 | return 0 | |
320 | ||
321 | print PRINT_BRACKET + " Session daemon spawned (pid: %d)\n" % (dem_pid) | |
322 | ||
323 | if not no_stats: | |
324 | mem_before = get_mem_usage(dem_pid) | |
325 | print PRINT_BRACKET + " Stats *before* test:" | |
326 | print PRINT_ARROW + " Mem (kB): %d" % (mem_before) | |
327 | cpu_count, cpu_stats = get_cpu_usage(pid = dem_pid) | |
328 | print_cpu_stats(cpu_stats, cpu_count) | |
329 | ||
330 | tw = TestWorker(".", test['bin']) | |
331 | tw.start() | |
332 | ||
333 | if not no_stats: | |
334 | # Start CPU sampling for test | |
335 | sw_cpu = SamplingWorker("cpu", worker = tw, pid = dem_pid) | |
336 | sw_cpu.start() | |
337 | sw_mem = SamplingWorker("mem", worker = tw, pid = dem_pid) | |
338 | sw_mem.start() | |
339 | ||
340 | ret = test_ret_q.get(True) | |
341 | ||
342 | if not no_stats: | |
343 | time.sleep(2) | |
344 | # Compute memory average | |
345 | mem_count, mem_during = mem_ret_q.get(True) | |
346 | mem_during = float(mem_during) / float(mem_count) | |
347 | cpu_count, cpu_stats = cpu_ret_q.get(True) | |
348 | ||
349 | print "\n" + PRINT_BRACKET + " Stats *during* test:" | |
350 | print PRINT_ARROW + " Mem (kB): %.0f [sampled %d time(s)]" % (mem_during, mem_count) | |
351 | print_cpu_stats(cpu_stats, cpu_count) | |
352 | ||
353 | mem_after = get_mem_usage(dem_pid) | |
354 | print "\n" + PRINT_BRACKET + " Stats *after* test:" | |
355 | print PRINT_ARROW + " Mem (kB): %d" % (mem_after) | |
356 | cpu_count, cpu_stats = get_cpu_usage(pid = dem_pid) | |
357 | print_cpu_stats(cpu_stats, cpu_count) | |
358 | ||
359 | print "\n" + PRINT_BRACKET + " Memory usage differences:" | |
360 | print PRINT_ARROW + " Diff during and before (kB): %d" % (mem_during - mem_before) | |
361 | print PRINT_ARROW + " Diff during and after (kB): %d" % (mem_during - mem_after) | |
362 | print PRINT_ARROW + " Diff before and after (kB): %d" % (mem_after - mem_before) | |
363 | ||
364 | # Return value of 0 means that is passed else it failed | |
365 | ret = print_test_success(ret, test['success']) | |
366 | ||
367 | # Stop session daemon | |
368 | if dem_pid > 0: | |
369 | print PRINT_BRACKET + " Stopping session daemon (pid: %d)..." % (dem_pid) | |
370 | try: | |
371 | os.kill(dem_pid, SIGTERM) | |
372 | # This call simply does not work... It seems python does not relay the signal | |
373 | # to the child processes of sdaemon_proc. | |
374 | # sdaemon_proc.terminate() | |
375 | if ret != 0: | |
376 | print sdaemon_proc.communicate()[1] | |
377 | elif sdaemon_proc.returncode == None: | |
378 | sdaemon_proc.communicate() | |
379 | except OSError, e: | |
380 | print e | |
381 | ||
382 | # Make sure all thread are released | |
383 | if not no_stats: | |
384 | tw.join() | |
385 | sw_cpu.join() | |
386 | sw_mem.join() | |
387 | ||
388 | return ret | |
389 | ||
390 | def main(): | |
391 | for test in Tests: | |
392 | if not test['enabled']: | |
393 | continue | |
394 | ||
395 | ret = run_test(test) | |
396 | if ret != 0: | |
397 | # Stop all tests, the last one failed | |
398 | return | |
399 | print "" | |
400 | ||
401 | def cleanup(signo, stack): | |
402 | """ Cleanup function """ | |
403 | sys.exit(0) | |
404 | ||
405 | if __name__ == "__main__": | |
406 | if not os.getuid() == 0: | |
407 | is_root = 0 | |
408 | print "NOTICE: Not root. No kernel tracing will be tested\n" | |
409 | ||
410 | if os.path.isfile("test_list.py"): | |
411 | from test_list import Tests | |
412 | else: | |
413 | print "No test_list.py found. Stopping" | |
414 | cleanup(0, 0) | |
415 | ||
416 | TESTDIR_PATH = os.getcwd() | |
417 | ||
418 | if len(sys.argv) > 1: | |
419 | if sys.argv[1] == "--no-stats": | |
420 | no_stats = 1 | |
421 | ||
422 | try: | |
423 | signal(SIGTERM, cleanup) | |
424 | signal(SIGINT, cleanup) | |
425 | main() | |
426 | cleanup(0, 0) | |
427 | except KeyboardInterrupt: | |
428 | cleanup(0, 0) |