Sasha Goldshtein | 8e583cc | 2017-02-09 10:11:50 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # syscount Summarize syscall counts and latencies. |
| 4 | # |
| 5 | # USAGE: syscount [-p PID] [-i INTERVAL] [-T TOP] [-x] [-L] [-m] [-P] [-l] |
| 6 | # |
| 7 | # Copyright 2017, Sasha Goldshtein. |
| 8 | # Licensed under the Apache License, Version 2.0 (the "License") |
| 9 | # |
| 10 | # 15-Feb-2017 Sasha Goldshtein Created this. |
| 11 | |
| 12 | from bcc import BPF |
Sasha Goldshtein | 8e583cc | 2017-02-09 10:11:50 -0500 | [diff] [blame] | 13 | from time import sleep, strftime |
| 14 | import argparse |
Sasha Goldshtein | 6545390 | 2017-03-26 13:06:51 +0000 | [diff] [blame] | 15 | import itertools |
Sasha Goldshtein | 51803bf | 2017-03-09 13:43:27 +0000 | [diff] [blame] | 16 | import subprocess |
Sasha Goldshtein | 8e583cc | 2017-02-09 10:11:50 -0500 | [diff] [blame] | 17 | import sys |
| 18 | |
Sasha Goldshtein | 6545390 | 2017-03-26 13:06:51 +0000 | [diff] [blame] | 19 | if sys.version_info.major < 3: |
| 20 | izip_longest = itertools.izip_longest |
| 21 | else: |
| 22 | izip_longest = itertools.zip_longest |
| 23 | |
Sasha Goldshtein | 8e583cc | 2017-02-09 10:11:50 -0500 | [diff] [blame] | 24 | # |
| 25 | # Syscall table for Linux x86_64, not very recent. |
| 26 | # Automatically generated from strace/linux/x86_64/syscallent.h using the |
| 27 | # following command: |
| 28 | # |
| 29 | # cat syscallent.h | awk -F, '{ gsub(/[ \t"}]/, "", $4); |
| 30 | # gsub(/[ \t/*]/, "", $5); |
| 31 | # print " "$5": \""$4"\","; } |
Paul Chaignon | 956ca1c | 2017-03-04 20:07:56 +0100 | [diff] [blame] | 32 | # BEGIN { print "syscalls = {" } |
| 33 | # END { print "}" }' |
Sasha Goldshtein | 8e583cc | 2017-02-09 10:11:50 -0500 | [diff] [blame] | 34 | # |
| 35 | syscalls = { |
| 36 | 0: "read", |
| 37 | 1: "write", |
| 38 | 2: "open", |
| 39 | 3: "close", |
| 40 | 4: "stat", |
| 41 | 5: "fstat", |
| 42 | 6: "lstat", |
| 43 | 7: "poll", |
| 44 | 8: "lseek", |
| 45 | 9: "mmap", |
| 46 | 10: "mprotect", |
| 47 | 11: "munmap", |
| 48 | 12: "brk", |
| 49 | 13: "rt_sigaction", |
| 50 | 14: "rt_sigprocmask", |
| 51 | 15: "rt_sigreturn", |
| 52 | 16: "ioctl", |
| 53 | 17: "pread", |
| 54 | 18: "pwrite", |
| 55 | 19: "readv", |
| 56 | 20: "writev", |
| 57 | 21: "access", |
| 58 | 22: "pipe", |
| 59 | 23: "select", |
| 60 | 24: "sched_yield", |
| 61 | 25: "mremap", |
| 62 | 26: "msync", |
| 63 | 27: "mincore", |
| 64 | 28: "madvise", |
| 65 | 29: "shmget", |
| 66 | 30: "shmat", |
| 67 | 31: "shmctl", |
| 68 | 32: "dup", |
| 69 | 33: "dup2", |
| 70 | 34: "pause", |
| 71 | 35: "nanosleep", |
| 72 | 36: "getitimer", |
| 73 | 37: "alarm", |
| 74 | 38: "setitimer", |
| 75 | 39: "getpid", |
| 76 | 40: "sendfile", |
| 77 | 41: "socket", |
| 78 | 42: "connect", |
| 79 | 43: "accept", |
| 80 | 44: "sendto", |
| 81 | 45: "recvfrom", |
| 82 | 46: "sendmsg", |
| 83 | 47: "recvmsg", |
| 84 | 48: "shutdown", |
| 85 | 49: "bind", |
| 86 | 50: "listen", |
| 87 | 51: "getsockname", |
| 88 | 52: "getpeername", |
| 89 | 53: "socketpair", |
| 90 | 54: "setsockopt", |
| 91 | 55: "getsockopt", |
| 92 | 56: "clone", |
| 93 | 57: "fork", |
| 94 | 58: "vfork", |
| 95 | 59: "execve", |
| 96 | 60: "_exit", |
| 97 | 61: "wait4", |
| 98 | 62: "kill", |
| 99 | 63: "uname", |
| 100 | 64: "semget", |
| 101 | 65: "semop", |
| 102 | 66: "semctl", |
| 103 | 67: "shmdt", |
| 104 | 68: "msgget", |
| 105 | 69: "msgsnd", |
| 106 | 70: "msgrcv", |
| 107 | 71: "msgctl", |
| 108 | 72: "fcntl", |
| 109 | 73: "flock", |
| 110 | 74: "fsync", |
| 111 | 75: "fdatasync", |
| 112 | 76: "truncate", |
| 113 | 77: "ftruncate", |
| 114 | 78: "getdents", |
| 115 | 79: "getcwd", |
| 116 | 80: "chdir", |
| 117 | 81: "fchdir", |
| 118 | 82: "rename", |
| 119 | 83: "mkdir", |
| 120 | 84: "rmdir", |
| 121 | 85: "creat", |
| 122 | 86: "link", |
| 123 | 87: "unlink", |
| 124 | 88: "symlink", |
| 125 | 89: "readlink", |
| 126 | 90: "chmod", |
| 127 | 91: "fchmod", |
| 128 | 92: "chown", |
| 129 | 93: "fchown", |
| 130 | 94: "lchown", |
| 131 | 95: "umask", |
| 132 | 96: "gettimeofday", |
| 133 | 97: "getrlimit", |
| 134 | 98: "getrusage", |
| 135 | 99: "sysinfo", |
| 136 | 100: "times", |
| 137 | 101: "ptrace", |
| 138 | 102: "getuid", |
| 139 | 103: "syslog", |
| 140 | 104: "getgid", |
| 141 | 105: "setuid", |
| 142 | 106: "setgid", |
| 143 | 107: "geteuid", |
| 144 | 108: "getegid", |
| 145 | 109: "setpgid", |
| 146 | 110: "getppid", |
| 147 | 111: "getpgrp", |
| 148 | 112: "setsid", |
| 149 | 113: "setreuid", |
| 150 | 114: "setregid", |
| 151 | 115: "getgroups", |
| 152 | 116: "setgroups", |
| 153 | 117: "setresuid", |
| 154 | 118: "getresuid", |
| 155 | 119: "setresgid", |
| 156 | 120: "getresgid", |
| 157 | 121: "getpgid", |
| 158 | 122: "setfsuid", |
| 159 | 123: "setfsgid", |
| 160 | 124: "getsid", |
| 161 | 125: "capget", |
| 162 | 126: "capset", |
| 163 | 127: "rt_sigpending", |
| 164 | 128: "rt_sigtimedwait", |
| 165 | 129: "rt_sigqueueinfo", |
| 166 | 130: "rt_sigsuspend", |
| 167 | 131: "sigaltstack", |
| 168 | 132: "utime", |
| 169 | 133: "mknod", |
| 170 | 134: "uselib", |
| 171 | 135: "personality", |
| 172 | 136: "ustat", |
| 173 | 137: "statfs", |
| 174 | 138: "fstatfs", |
| 175 | 139: "sysfs", |
| 176 | 140: "getpriority", |
| 177 | 141: "setpriority", |
| 178 | 142: "sched_setparam", |
| 179 | 143: "sched_getparam", |
| 180 | 144: "sched_setscheduler", |
| 181 | 145: "sched_getscheduler", |
| 182 | 146: "sched_get_priority_max", |
| 183 | 147: "sched_get_priority_min", |
| 184 | 148: "sched_rr_get_interval", |
| 185 | 149: "mlock", |
| 186 | 150: "munlock", |
| 187 | 151: "mlockall", |
| 188 | 152: "munlockall", |
| 189 | 153: "vhangup", |
| 190 | 154: "modify_ldt", |
| 191 | 155: "pivot_root", |
| 192 | 156: "_sysctl", |
| 193 | 157: "prctl", |
| 194 | 158: "arch_prctl", |
| 195 | 159: "adjtimex", |
| 196 | 160: "setrlimit", |
| 197 | 161: "chroot", |
| 198 | 162: "sync", |
| 199 | 163: "acct", |
| 200 | 164: "settimeofday", |
| 201 | 165: "mount", |
| 202 | 166: "umount", |
| 203 | 167: "swapon", |
| 204 | 168: "swapoff", |
| 205 | 169: "reboot", |
| 206 | 170: "sethostname", |
| 207 | 171: "setdomainname", |
| 208 | 172: "iopl", |
| 209 | 173: "ioperm", |
| 210 | 174: "create_module", |
| 211 | 175: "init_module", |
| 212 | 176: "delete_module", |
| 213 | 177: "get_kernel_syms", |
| 214 | 178: "query_module", |
| 215 | 179: "quotactl", |
| 216 | 180: "nfsservctl", |
| 217 | 181: "getpmsg", |
| 218 | 182: "putpmsg", |
| 219 | 183: "afs_syscall", |
| 220 | 184: "tuxcall", |
| 221 | 185: "security", |
| 222 | 186: "gettid", |
| 223 | 187: "readahead", |
| 224 | 188: "setxattr", |
| 225 | 189: "lsetxattr", |
| 226 | 190: "fsetxattr", |
| 227 | 191: "getxattr", |
| 228 | 192: "lgetxattr", |
| 229 | 193: "fgetxattr", |
| 230 | 194: "listxattr", |
| 231 | 195: "llistxattr", |
| 232 | 196: "flistxattr", |
| 233 | 197: "removexattr", |
| 234 | 198: "lremovexattr", |
| 235 | 199: "fremovexattr", |
| 236 | 200: "tkill", |
| 237 | 201: "time", |
| 238 | 202: "futex", |
| 239 | 203: "sched_setaffinity", |
| 240 | 204: "sched_getaffinity", |
| 241 | 205: "set_thread_area", |
| 242 | 206: "io_setup", |
| 243 | 207: "io_destroy", |
| 244 | 208: "io_getevents", |
| 245 | 209: "io_submit", |
| 246 | 210: "io_cancel", |
| 247 | 211: "get_thread_area", |
| 248 | 212: "lookup_dcookie", |
| 249 | 213: "epoll_create", |
| 250 | 214: "epoll_ctl_old", |
| 251 | 215: "epoll_wait_old", |
| 252 | 216: "remap_file_pages", |
| 253 | 217: "getdents64", |
| 254 | 218: "set_tid_address", |
| 255 | 219: "restart_syscall", |
| 256 | 220: "semtimedop", |
| 257 | 221: "fadvise64", |
| 258 | 222: "timer_create", |
| 259 | 223: "timer_settime", |
| 260 | 224: "timer_gettime", |
| 261 | 225: "timer_getoverrun", |
| 262 | 226: "timer_delete", |
| 263 | 227: "clock_settime", |
| 264 | 228: "clock_gettime", |
| 265 | 229: "clock_getres", |
| 266 | 230: "clock_nanosleep", |
| 267 | 231: "exit_group", |
| 268 | 232: "epoll_wait", |
| 269 | 233: "epoll_ctl", |
| 270 | 234: "tgkill", |
| 271 | 235: "utimes", |
| 272 | 236: "vserver", |
| 273 | 237: "mbind", |
| 274 | 238: "set_mempolicy", |
| 275 | 239: "get_mempolicy", |
| 276 | 240: "mq_open", |
| 277 | 241: "mq_unlink", |
| 278 | 242: "mq_timedsend", |
| 279 | 243: "mq_timedreceive", |
| 280 | 244: "mq_notify", |
| 281 | 245: "mq_getsetattr", |
| 282 | 246: "kexec_load", |
| 283 | 247: "waitid", |
| 284 | 248: "add_key", |
| 285 | 249: "request_key", |
| 286 | 250: "keyctl", |
| 287 | 251: "ioprio_set", |
| 288 | 252: "ioprio_get", |
| 289 | 253: "inotify_init", |
| 290 | 254: "inotify_add_watch", |
| 291 | 255: "inotify_rm_watch", |
| 292 | 256: "migrate_pages", |
| 293 | 257: "openat", |
| 294 | 258: "mkdirat", |
| 295 | 259: "mknodat", |
| 296 | 260: "fchownat", |
| 297 | 261: "futimesat", |
| 298 | 262: "newfstatat", |
| 299 | 263: "unlinkat", |
| 300 | 264: "renameat", |
| 301 | 265: "linkat", |
| 302 | 266: "symlinkat", |
| 303 | 267: "readlinkat", |
| 304 | 268: "fchmodat", |
| 305 | 269: "faccessat", |
| 306 | 270: "pselect6", |
| 307 | 271: "ppoll", |
| 308 | 272: "unshare", |
| 309 | 273: "set_robust_list", |
| 310 | 274: "get_robust_list", |
| 311 | 275: "splice", |
| 312 | 276: "tee", |
| 313 | 277: "sync_file_range", |
| 314 | 278: "vmsplice", |
| 315 | 279: "move_pages", |
| 316 | 280: "utimensat", |
| 317 | 281: "epoll_pwait", |
| 318 | 282: "signalfd", |
| 319 | 283: "timerfd_create", |
| 320 | 284: "eventfd", |
| 321 | 285: "fallocate", |
| 322 | 286: "timerfd_settime", |
| 323 | 287: "timerfd_gettime", |
| 324 | 288: "accept4", |
| 325 | 289: "signalfd4", |
| 326 | 290: "eventfd2", |
| 327 | 291: "epoll_create1", |
| 328 | 292: "dup3", |
| 329 | 293: "pipe2", |
| 330 | 294: "inotify_init1", |
| 331 | 295: "preadv", |
| 332 | 296: "pwritev", |
| 333 | 297: "rt_tgsigqueueinfo", |
| 334 | 298: "perf_event_open", |
| 335 | 299: "recvmmsg", |
| 336 | 300: "fanotify_init", |
| 337 | 301: "fanotify_mark", |
| 338 | 302: "prlimit64", |
| 339 | 303: "name_to_handle_at", |
| 340 | 304: "open_by_handle_at", |
| 341 | 305: "clock_adjtime", |
| 342 | 306: "syncfs", |
| 343 | 307: "sendmmsg", |
| 344 | 308: "setns", |
| 345 | 309: "getcpu", |
| 346 | 310: "process_vm_readv", |
| 347 | 311: "process_vm_writev", |
| 348 | 312: "kcmp", |
| 349 | 313: "finit_module", |
| 350 | } |
| 351 | |
Sasha Goldshtein | 51803bf | 2017-03-09 13:43:27 +0000 | [diff] [blame] | 352 | # Try to use ausyscall if it is available, because it can give us an up-to-date |
| 353 | # list of syscalls for various architectures, rather than the x86-64 hardcoded |
| 354 | # list above. |
| 355 | def parse_syscall(line): |
| 356 | parts = line.split() |
| 357 | return (int(parts[0]), parts[1].strip()) |
| 358 | |
| 359 | try: |
| 360 | # Skip the first line, which is a header. The rest of the lines are simply |
| 361 | # SYSCALL_NUM\tSYSCALL_NAME pairs. |
| 362 | out = subprocess.check_output('ausyscall --dump | tail -n +2', shell=True) |
| 363 | syscalls = dict(map(parse_syscall, out.strip().split('\n'))) |
| 364 | except Exception as e: |
| 365 | pass |
| 366 | |
Sasha Goldshtein | 8e583cc | 2017-02-09 10:11:50 -0500 | [diff] [blame] | 367 | parser = argparse.ArgumentParser( |
| 368 | description="Summarize syscall counts and latencies.") |
| 369 | parser.add_argument("-p", "--pid", type=int, help="trace only this pid") |
| 370 | parser.add_argument("-i", "--interval", type=int, |
| 371 | help="print summary at this interval (seconds)") |
| 372 | parser.add_argument("-T", "--top", type=int, default=10, |
| 373 | help="print only the top syscalls by count or latency") |
| 374 | parser.add_argument("-x", "--failures", action="store_true", |
| 375 | help="trace only failed syscalls (return < 0)") |
| 376 | parser.add_argument("-L", "--latency", action="store_true", |
| 377 | help="collect syscall latency") |
| 378 | parser.add_argument("-m", "--milliseconds", action="store_true", |
| 379 | help="display latency in milliseconds (default: microseconds)") |
| 380 | parser.add_argument("-P", "--process", action="store_true", |
| 381 | help="count by process and not by syscall") |
| 382 | parser.add_argument("-l", "--list", action="store_true", |
| 383 | help="print list of recognized syscalls and exit") |
| 384 | args = parser.parse_args() |
| 385 | |
| 386 | if args.list: |
| 387 | for grp in izip_longest(*(iter(sorted(syscalls.values())),) * 4): |
| 388 | print(" ".join(["%-20s" % s for s in grp if s is not None])) |
| 389 | sys.exit(0) |
| 390 | |
| 391 | text = """ |
| 392 | #ifdef LATENCY |
| 393 | struct data_t { |
| 394 | u64 count; |
| 395 | u64 total_ns; |
| 396 | }; |
| 397 | |
| 398 | BPF_HASH(start, u64, u64); |
| 399 | BPF_HASH(data, u32, struct data_t); |
| 400 | #else |
| 401 | BPF_HASH(data, u32, u64); |
| 402 | #endif |
| 403 | |
| 404 | #ifdef LATENCY |
| 405 | TRACEPOINT_PROBE(raw_syscalls, sys_enter) { |
| 406 | u64 pid_tgid = bpf_get_current_pid_tgid(); |
| 407 | |
| 408 | #ifdef FILTER_PID |
| 409 | if (pid_tgid >> 32 != FILTER_PID) |
| 410 | return 0; |
| 411 | #endif |
| 412 | |
| 413 | u64 t = bpf_ktime_get_ns(); |
| 414 | start.update(&pid_tgid, &t); |
| 415 | return 0; |
| 416 | } |
| 417 | #endif |
| 418 | |
| 419 | TRACEPOINT_PROBE(raw_syscalls, sys_exit) { |
| 420 | u64 pid_tgid = bpf_get_current_pid_tgid(); |
| 421 | |
| 422 | #ifdef FILTER_PID |
| 423 | if (pid_tgid >> 32 != FILTER_PID) |
| 424 | return 0; |
| 425 | #endif |
| 426 | |
| 427 | #ifdef FILTER_FAILED |
| 428 | if (args->ret >= 0) |
| 429 | return 0; |
| 430 | #endif |
| 431 | |
| 432 | #ifdef BY_PROCESS |
| 433 | u32 key = pid_tgid >> 32; |
| 434 | #else |
| 435 | u32 key = args->id; |
| 436 | #endif |
| 437 | |
| 438 | #ifdef LATENCY |
| 439 | struct data_t *val, zero = {}; |
| 440 | u64 *start_ns = start.lookup(&pid_tgid); |
| 441 | if (!start_ns) |
| 442 | return 0; |
| 443 | |
| 444 | val = data.lookup_or_init(&key, &zero); |
| 445 | val->count++; |
| 446 | val->total_ns = bpf_ktime_get_ns() - *start_ns; |
| 447 | #else |
| 448 | u64 *val, zero = 0; |
| 449 | val = data.lookup_or_init(&key, &zero); |
| 450 | ++(*val); |
| 451 | #endif |
| 452 | return 0; |
| 453 | } |
| 454 | """ |
| 455 | |
| 456 | if args.pid: |
| 457 | text = ("#define FILTER_PID %d\n" % args.pid) + text |
| 458 | if args.failures: |
| 459 | text = "#define FILTER_FAILED\n" + text |
| 460 | if args.latency: |
| 461 | text = "#define LATENCY\n" + text |
| 462 | if args.process: |
| 463 | text = "#define BY_PROCESS\n" + text |
| 464 | |
| 465 | bpf = BPF(text=text) |
| 466 | |
| 467 | def print_stats(): |
| 468 | if args.latency: |
| 469 | print_latency_stats() |
| 470 | else: |
| 471 | print_count_stats() |
| 472 | |
| 473 | agg_colname = "PID COMM" if args.process else "SYSCALL" |
| 474 | time_colname = "TIME (ms)" if args.milliseconds else "TIME (us)" |
| 475 | |
| 476 | def comm_for_pid(pid): |
| 477 | try: |
| 478 | return open("/proc/%d/comm" % pid).read().strip() |
| 479 | except Exception: |
| 480 | return "[unknown]" |
| 481 | |
| 482 | def agg_colval(key): |
| 483 | if args.process: |
Paul Chaignon | 956ca1c | 2017-03-04 20:07:56 +0100 | [diff] [blame] | 484 | return "%-6d %-15s" % (key.value, comm_for_pid(key.value)) |
Sasha Goldshtein | 8e583cc | 2017-02-09 10:11:50 -0500 | [diff] [blame] | 485 | else: |
| 486 | return syscalls.get(key.value, "[unknown: %d]" % key.value) |
| 487 | |
| 488 | def print_count_stats(): |
| 489 | data = bpf["data"] |
| 490 | print("[%s]" % strftime("%H:%M:%S")) |
| 491 | print("%-22s %8s" % (agg_colname, "COUNT")) |
| 492 | for k, v in sorted(data.items(), key=lambda kv: -kv[1].value)[:args.top]: |
| 493 | if k.value == 0xFFFFFFFF: |
| 494 | continue # happens occasionally, we don't need it |
| 495 | print("%-22s %8d" % (agg_colval(k), v.value)) |
| 496 | print("") |
| 497 | data.clear() |
| 498 | |
| 499 | def print_latency_stats(): |
| 500 | data = bpf["data"] |
| 501 | print("[%s]" % strftime("%H:%M:%S")) |
| 502 | print("%-22s %8s %16s" % (agg_colname, "COUNT", time_colname)) |
Paul Chaignon | 956ca1c | 2017-03-04 20:07:56 +0100 | [diff] [blame] | 503 | for k, v in sorted(data.items(), |
| 504 | key=lambda kv: -kv[1].total_ns)[:args.top]: |
Sasha Goldshtein | 8e583cc | 2017-02-09 10:11:50 -0500 | [diff] [blame] | 505 | if k.value == 0xFFFFFFFF: |
| 506 | continue # happens occasionally, we don't need it |
| 507 | print(("%-22s %8d " + ("%16.6f" if args.milliseconds else "%16.3f")) % |
| 508 | (agg_colval(k), v.count, |
| 509 | v.total_ns / (1e6 if args.milliseconds else 1e3))) |
| 510 | print("") |
| 511 | data.clear() |
| 512 | |
| 513 | print("Tracing %ssyscalls, printing top %d... Ctrl+C to quit." % |
| 514 | ("failed " if args.failures else "", args.top)) |
| 515 | while True: |
| 516 | try: |
| 517 | sleep(args.interval or 999999999) |
| 518 | print_stats() |
| 519 | except KeyboardInterrupt: |
| 520 | if not args.interval: |
| 521 | print_stats() |
| 522 | break |