1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
|
#!/usr/bin/env python
#
# syscount Summarize syscall counts and latencies.
#
# USAGE: syscount [-h] [-p PID] [-t TID] [-i INTERVAL] [-d DURATION] [-T TOP]
# [-x] [-e ERRNO] [-L] [-m] [-P] [-l] [--syscall SYSCALL]
#
# Copyright 2017, Sasha Goldshtein.
# Licensed under the Apache License, Version 2.0 (the "License")
#
# 15-Feb-2017 Sasha Goldshtein Created this.
# 16-May-2022 Rocky Xing Added TID filter support.
# 26-Jul-2022 Rocky Xing Added syscall filter support.
from time import sleep, strftime
import argparse
import errno
import itertools
import sys
import signal
from bcc import BPF
from bcc.utils import printb
from bcc.syscall import syscall_name, syscalls
if sys.version_info.major < 3:
izip_longest = itertools.izip_longest
else:
izip_longest = itertools.zip_longest
# signal handler
def signal_ignore(signal, frame):
print()
def handle_errno(errstr):
try:
return abs(int(errstr))
except ValueError:
pass
try:
return getattr(errno, errstr)
except AttributeError:
raise argparse.ArgumentTypeError("couldn't map %s to an errno" % errstr)
parser = argparse.ArgumentParser(
description="Summarize syscall counts and latencies.")
parser.add_argument("-p", "--pid", type=int,
help="trace only this pid")
parser.add_argument("-t", "--tid", type=int,
help="trace only this tid")
parser.add_argument("-c", "--ppid", type=int,
help="trace only child of this pid")
parser.add_argument("-i", "--interval", type=int,
help="print summary at this interval (seconds)")
parser.add_argument("-d", "--duration", type=int,
help="total duration of trace, in seconds")
parser.add_argument("-T", "--top", type=int, default=10,
help="print only the top syscalls by count or latency")
parser.add_argument("-x", "--failures", action="store_true",
help="trace only failed syscalls (return < 0)")
parser.add_argument("-e", "--errno", type=handle_errno,
help="trace only syscalls that return this error (numeric or EPERM, etc.)")
parser.add_argument("-L", "--latency", action="store_true",
help="collect syscall latency")
parser.add_argument("-m", "--milliseconds", action="store_true",
help="display latency in milliseconds (default: microseconds)")
parser.add_argument("-P", "--process", action="store_true",
help="count by process and not by syscall")
parser.add_argument("-l", "--list", action="store_true",
help="print list of recognized syscalls and exit")
parser.add_argument("--syscall", type=str,
help="trace this syscall only (use option -l to get all recognized syscalls)")
parser.add_argument("--ebpf", action="store_true",
help=argparse.SUPPRESS)
args = parser.parse_args()
if args.duration and not args.interval:
args.interval = args.duration
if not args.interval:
args.interval = 99999999
syscall_nr = -1
if args.syscall is not None:
syscall = bytes(args.syscall, 'utf-8')
for key, value in syscalls.items():
if syscall == value:
syscall_nr = key
break
if syscall_nr == -1:
print("Error: syscall '%s' not found. Exiting." % args.syscall)
sys.exit(1)
if args.list:
for grp in izip_longest(*(iter(sorted(syscalls.values())),) * 4):
print(" ".join(["%-22s" % s.decode() for s in grp if s is not None]))
sys.exit(0)
text = """
#include <linux/sched.h>
#ifdef LATENCY
struct data_t {
u64 count;
u64 total_ns;
};
BPF_HASH(start, u64, u64);
BPF_HASH(data, u32, struct data_t);
#else
BPF_HASH(data, u32, u64);
#endif
#ifdef LATENCY
TRACEPOINT_PROBE(raw_syscalls, sys_enter) {
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
u32 tid = (u32)pid_tgid;
#ifdef FILTER_SYSCALL_NR
if (args->id != FILTER_SYSCALL_NR)
return 0;
#endif
#ifdef FILTER_PID
if (pid != FILTER_PID)
return 0;
#endif
#ifdef FILTER_TID
if (tid != FILTER_TID)
return 0;
#endif
#ifdef FILTER_PPID
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
u32 ppid = task->real_parent->tgid;
if (ppid != FILTER_PPID)
return 0;
#endif
u64 t = bpf_ktime_get_ns();
start.update(&pid_tgid, &t);
return 0;
}
#endif
TRACEPOINT_PROBE(raw_syscalls, sys_exit) {
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
u32 tid = (u32)pid_tgid;
#ifdef FILTER_SYSCALL_NR
if (args->id != FILTER_SYSCALL_NR)
return 0;
#endif
#ifdef FILTER_PID
if (pid != FILTER_PID)
return 0;
#endif
#ifdef FILTER_TID
if (tid != FILTER_TID)
return 0;
#endif
#ifdef FILTER_PPID
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
u32 ppid = task->real_parent->tgid;
if (ppid != FILTER_PPID)
return 0;
#endif
#ifdef FILTER_FAILED
if (args->ret >= 0)
return 0;
#endif
#ifdef FILTER_ERRNO
if (args->ret != -FILTER_ERRNO)
return 0;
#endif
#ifdef BY_PROCESS
u32 key = pid_tgid >> 32;
#else
u32 key = args->id;
#endif
#ifdef LATENCY
struct data_t *val, zero = {};
u64 *start_ns = start.lookup(&pid_tgid);
if (!start_ns)
return 0;
val = data.lookup_or_try_init(&key, &zero);
if (val) {
lock_xadd(&val->count, 1);
lock_xadd(&val->total_ns, bpf_ktime_get_ns() - *start_ns);
}
#else
u64 *val, zero = 0;
val = data.lookup_or_try_init(&key, &zero);
if (val) {
lock_xadd(val, 1);
}
#endif
return 0;
}
"""
if args.pid:
text = ("#define FILTER_PID %d\n" % args.pid) + text
elif args.tid:
text = ("#define FILTER_TID %d\n" % args.tid) + text
elif args.ppid:
text = ("#define FILTER_PPID %d\n" % args.ppid) + text
if args.failures:
text = "#define FILTER_FAILED\n" + text
if args.errno:
text = "#define FILTER_ERRNO %d\n" % abs(args.errno) + text
if args.latency:
text = "#define LATENCY\n" + text
if args.process:
text = "#define BY_PROCESS\n" + text
if args.syscall is not None:
text = ("#define FILTER_SYSCALL_NR %d\n" % syscall_nr) + text
if args.ebpf:
print(text)
exit()
bpf = BPF(text=text)
def print_stats():
if args.latency:
print_latency_stats()
else:
print_count_stats()
agg_colname = "PID COMM" if args.process else "SYSCALL"
time_colname = "TIME (ms)" if args.milliseconds else "TIME (us)"
def comm_for_pid(pid):
try:
return open("/proc/%d/comm" % pid, "rb").read().strip()
except Exception:
return b"[unknown]"
def agg_colval(key):
if args.process:
return b"%-6d %-15s" % (key.value, comm_for_pid(key.value))
else:
return syscall_name(key.value)
# check whether hash table batch ops is supported
htab_batch_ops = True if BPF.kernel_struct_has_field(b'bpf_map_ops',
b'map_lookup_and_delete_batch') == 1 else False
def print_count_stats():
data = bpf["data"]
print("[%s]" % strftime("%H:%M:%S"))
print("%-22s %8s" % (agg_colname, "COUNT"))
for k, v in sorted(data.items_lookup_and_delete_batch()
if htab_batch_ops else data.items(),
key=lambda kv: -kv[1].value)[:args.top]:
if k.value == 0xFFFFFFFF:
continue # happens occasionally, we don't need it
printb(b"%-22s %8d" % (agg_colval(k), v.value))
print("")
if not htab_batch_ops:
data.clear()
def print_latency_stats():
data = bpf["data"]
print("[%s]" % strftime("%H:%M:%S"))
print("%-22s %8s %16s" % (agg_colname, "COUNT", time_colname))
for k, v in sorted(data.items_lookup_and_delete_batch()
if htab_batch_ops else data.items(),
key=lambda kv: -kv[1].total_ns)[:args.top]:
if k.value == 0xFFFFFFFF:
continue # happens occasionally, we don't need it
printb((b"%-22s %8d " + (b"%16.6f" if args.milliseconds else b"%16.3f")) %
(agg_colval(k), v.count,
v.total_ns / (1e6 if args.milliseconds else 1e3)))
print("")
if not htab_batch_ops:
data.clear()
if args.syscall is not None:
print("Tracing %ssyscall '%s'... Ctrl+C to quit." %
("failed " if args.failures else "", args.syscall))
else:
print("Tracing %ssyscalls, printing top %d... Ctrl+C to quit." %
("failed " if args.failures else "", args.top))
exiting = 0 if args.interval else 1
seconds = 0
while True:
try:
sleep(args.interval)
seconds += args.interval
except KeyboardInterrupt:
exiting = 1
signal.signal(signal.SIGINT, signal_ignore)
if args.duration and seconds >= args.duration:
exiting = 1
print_stats()
if exiting:
print("Detaching...")
exit()
|