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 | ||
49 | for word in words: | |
50 | index = word.find('%') | |
51 | # Add the value to the dictionnary | |
52 | top_dict[word[index + 1:]] = float(word[:index]) | |
53 | ||
54 | return top_dict | |
55 | ||
56 | def cpu_average_usage(top_lines): | |
57 | """ | |
58 | Return a dictionnary of 'top' CPU stats but averaging all values. | |
59 | """ | |
60 | avg_dict = {'us': 0, 'sy': 0, 'ni': 0, 'id': 0, 'wa': 0, 'hi': 0, 'si': 0, 'st': 0} | |
61 | # Average count | |
62 | count = 0.0 | |
63 | ||
64 | for line in top_lines: | |
65 | tmp_dict = cpu_create_usage_dict(line) | |
66 | # Add value to avg dictionnary | |
67 | for key in tmp_dict: | |
68 | avg_dict[key] += tmp_dict[key] | |
69 | ||
70 | count += 1.0 | |
71 | ||
72 | for key in avg_dict: | |
73 | avg_dict[key] = avg_dict[key] / count | |
74 | ||
75 | return (count, avg_dict) | |
76 | ||
77 | def cpu_sample_usage(pid=None): | |
78 | """ | |
79 | Sample CPU usage for num iterations. | |
80 | If num is greater than 1, the average will be computed. | |
81 | """ | |
82 | args = ["top", "-b", "-n", "1"] | |
83 | if pid: | |
84 | args.append("-p") | |
85 | args.append(str(pid)) | |
86 | ||
87 | # Spawn top process | |
88 | top = subprocess.Popen(args, stdout = subprocess.PIPE) | |
89 | ||
90 | grep = subprocess.Popen(["grep", "^Cpu"], stdin = top.stdout, | |
91 | stdout = subprocess.PIPE) | |
92 | top.stdout.close() | |
93 | ||
94 | return grep.communicate()[0].strip("\n") | |
95 | ||
96 | def mem_sample_usage(pid): | |
97 | """ | |
98 | Sample memory usage using /proc and a pid | |
99 | """ | |
100 | args = ["cat", "/proc/" + str(pid) + "/status"] | |
101 | ||
102 | if not os.path.isfile(args[1]): | |
103 | return -1 | |
104 | ||
105 | mem_proc = subprocess.Popen(args, stdout = subprocess.PIPE) | |
106 | ||
107 | grep = subprocess.Popen(["grep", "^VmRSS"], stdin = mem_proc.stdout, | |
108 | stdout = subprocess.PIPE) | |
109 | mem_proc.stdout.close() | |
110 | ||
111 | # Return virtual memory size in kilobytes (kB) | |
112 | #ret = grep.communicate()[0].split() | |
113 | ret = grep.communicate()[0].split() | |
114 | ||
115 | if len(ret) > 1: | |
116 | ret = ret[1] | |
117 | else: | |
118 | ret = 0 | |
119 | ||
120 | return int(ret) | |
121 | ||
122 | class SamplingWorker(threading.Thread): | |
123 | def __init__(self, s_type, worker = None, delay = 0.2, pid = 0): | |
124 | threading.Thread.__init__ (self) | |
125 | self.s_type = s_type | |
126 | self.delay = delay | |
127 | self.pid = pid | |
128 | self.worker = worker | |
129 | ||
130 | def run(self): | |
131 | count = 1 | |
132 | lines = [] | |
133 | ||
134 | if self.s_type == "cpu": | |
135 | while 1: | |
136 | if self.worker == None: | |
137 | cpu_line = cpu_sample_usage(self.pid) | |
138 | lines.append(cpu_line) | |
139 | break | |
140 | elif self.worker.is_alive(): | |
141 | cpu_line = cpu_sample_usage(self.pid) | |
142 | lines.append(cpu_line) | |
143 | else: | |
144 | break | |
145 | ||
146 | # Delay sec per memory sampling | |
147 | time.sleep(self.delay) | |
148 | ||
149 | count, stats = cpu_average_usage(lines) | |
150 | cpu_ret_q.put((count, stats)) | |
151 | # grep process has ended here | |
152 | ||
153 | elif self.s_type == "mem": | |
154 | count = 0 | |
155 | mem_stat = 0 | |
156 | ||
157 | while 1: | |
158 | if self.worker == None: | |
159 | cpu_line = cpu_sample_usage(self.pid) | |
160 | lines.append(cpu_line) | |
161 | break | |
162 | elif self.worker.is_alive(): | |
163 | mem_stat += get_mem_usage(self.pid) | |
164 | count += 1 | |
165 | else: | |
166 | break | |
167 | ||
168 | # Delay sec per memory sampling | |
169 | time.sleep(self.delay) | |
170 | ||
171 | mem_ret_q.put((count, mem_stat)) | |
172 | ||
173 | class TestWorker(threading.Thread): | |
174 | def __init__(self, path, name): | |
175 | threading.Thread.__init__(self) | |
176 | self.path = path | |
177 | self.name = name | |
178 | ||
179 | def run(self): | |
180 | bin_path_name = os.path.join(self.path, self.name) | |
181 | ||
182 | env = os.environ | |
183 | env['TEST_NO_SESSIOND'] = '1' | |
184 | ||
4ed5c01e | 185 | test = subprocess.Popen([bin_path_name], env=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 | ||
304 | # No session daemon needed | |
305 | if not test['daemon']: | |
306 | print PRINT_ARROW + " No session daemon needed" | |
307 | ret = start_test(test['bin']) | |
308 | print_test_success(ret, test['success']) | |
309 | return 0 | |
310 | else: | |
311 | print PRINT_ARROW + " Session daemon needed" | |
312 | ||
313 | dem_pid = spawn_session_daemon() | |
314 | if dem_pid <= 0: | |
315 | print "Unable to start %s. Stopping" % (SESSIOND_BIN_NAME) | |
316 | print sdaemon_proc.communicate()[1] | |
317 | return 0 | |
318 | ||
319 | print PRINT_BRACKET + " Session daemon spawned (pid: %d)\n" % (dem_pid) | |
320 | ||
321 | if not no_stats: | |
322 | mem_before = get_mem_usage(dem_pid) | |
323 | print PRINT_BRACKET + " Stats *before* test:" | |
324 | print PRINT_ARROW + " Mem (kB): %d" % (mem_before) | |
325 | cpu_count, cpu_stats = get_cpu_usage(pid = dem_pid) | |
326 | print_cpu_stats(cpu_stats, cpu_count) | |
327 | ||
328 | tw = TestWorker(".", test['bin']) | |
329 | tw.start() | |
330 | ||
331 | if not no_stats: | |
332 | # Start CPU sampling for test | |
333 | sw_cpu = SamplingWorker("cpu", worker = tw, pid = dem_pid) | |
334 | sw_cpu.start() | |
335 | sw_mem = SamplingWorker("mem", worker = tw, pid = dem_pid) | |
336 | sw_mem.start() | |
337 | ||
338 | ret = test_ret_q.get(True) | |
339 | ||
340 | if not no_stats: | |
341 | time.sleep(2) | |
342 | # Compute memory average | |
343 | mem_count, mem_during = mem_ret_q.get(True) | |
344 | mem_during = float(mem_during) / float(mem_count) | |
345 | cpu_count, cpu_stats = cpu_ret_q.get(True) | |
346 | ||
347 | print "\n" + PRINT_BRACKET + " Stats *during* test:" | |
348 | print PRINT_ARROW + " Mem (kB): %.0f [sampled %d time(s)]" % (mem_during, mem_count) | |
349 | print_cpu_stats(cpu_stats, cpu_count) | |
350 | ||
351 | mem_after = get_mem_usage(dem_pid) | |
352 | print "\n" + PRINT_BRACKET + " Stats *after* test:" | |
353 | print PRINT_ARROW + " Mem (kB): %d" % (mem_after) | |
354 | cpu_count, cpu_stats = get_cpu_usage(pid = dem_pid) | |
355 | print_cpu_stats(cpu_stats, cpu_count) | |
356 | ||
357 | print "\n" + PRINT_BRACKET + " Memory usage differences:" | |
358 | print PRINT_ARROW + " Diff during and before (kB): %d" % (mem_during - mem_before) | |
359 | print PRINT_ARROW + " Diff during and after (kB): %d" % (mem_during - mem_after) | |
360 | print PRINT_ARROW + " Diff before and after (kB): %d" % (mem_after - mem_before) | |
361 | ||
362 | # Return value of 0 means that is passed else it failed | |
363 | ret = print_test_success(ret, test['success']) | |
364 | ||
365 | # Stop session daemon | |
366 | if dem_pid > 0: | |
367 | print PRINT_BRACKET + " Stopping session daemon (pid: %d)..." % (dem_pid) | |
368 | try: | |
369 | os.kill(dem_pid, SIGTERM) | |
370 | # This call simply does not work... It seems python does not relay the signal | |
371 | # to the child processes of sdaemon_proc. | |
372 | # sdaemon_proc.terminate() | |
373 | if ret != 0: | |
374 | print sdaemon_proc.communicate()[1] | |
375 | elif sdaemon_proc.returncode == None: | |
376 | sdaemon_proc.communicate() | |
377 | except OSError, e: | |
378 | print e | |
379 | ||
380 | # Make sure all thread are released | |
381 | if not no_stats: | |
382 | tw.join() | |
383 | sw_cpu.join() | |
384 | sw_mem.join() | |
385 | ||
386 | return ret | |
387 | ||
388 | def main(): | |
389 | for test in Tests: | |
390 | if not test['enabled']: | |
391 | continue | |
392 | ||
393 | ret = run_test(test) | |
394 | if ret != 0: | |
395 | # Stop all tests, the last one failed | |
396 | return | |
397 | print "" | |
398 | ||
399 | def cleanup(signo, stack): | |
400 | """ Cleanup function """ | |
401 | sys.exit(0) | |
402 | ||
403 | if __name__ == "__main__": | |
404 | if not os.getuid() == 0: | |
405 | is_root = 0 | |
406 | print "NOTICE: Not root. No kernel tracing will be tested\n" | |
407 | ||
408 | if os.path.isfile("test_list.py"): | |
409 | from test_list import Tests | |
410 | else: | |
411 | print "No test_list.py found. Stopping" | |
412 | cleanup(0, 0) | |
413 | ||
414 | TESTDIR_PATH = os.getcwd() | |
415 | ||
416 | if len(sys.argv) > 1: | |
417 | if sys.argv[1] == "--no-stats": | |
418 | no_stats = 1 | |
419 | ||
420 | try: | |
421 | signal(SIGTERM, cleanup) | |
422 | signal(SIGINT, cleanup) | |
423 | main() | |
424 | cleanup(0, 0) | |
425 | except KeyboardInterrupt: | |
426 | cleanup(0, 0) |