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