run-report: Fix CPU usage stats computation
[lttng-tools.git] / tests / 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):
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
187 test = subprocess.Popen([bin_path_name], env=env, preexec_fn = lambda: signal(SIGPIPE, SIG_DFL))
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)
216 consumer_path = os.path.join(TESTDIR_PATH, "..", CONSUMERD_BIN_PATH, CONSUMERD_BIN_NAME)
217
218 if not os.path.isfile(bin_path):
219 print "Error: No session daemon binary found. Compiled?"
220 return 0
221
222 try:
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
229 except OSError, e:
230 print e
231 return 0
232
233 time.sleep(1)
234
235 return get_pid("lt-" + SESSIOND_BIN_NAME)
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']):
303 print "Unable to find test file '%s'. Skipping" % (test['bin'])
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)
This page took 0.037174 seconds and 4 git commands to generate.