1 #!/usr/bin/python 2 # @lint-avoid-python-3-compatibility-imports 3 # 4 # statsnoop Trace stat() syscalls. 5 # For Linux, uses BCC, eBPF. Embedded C. 6 # 7 # USAGE: statsnoop [-h] [-t] [-x] [-p PID] 8 # 9 # Copyright 2016 Netflix, Inc. 10 # Licensed under the Apache License, Version 2.0 (the "License") 11 # 12 # 08-Feb-2016 Brendan Gregg Created this. 13 # 17-Feb-2016 Allan McAleavy updated for BPF_PERF_OUTPUT 14 15 from __future__ import print_function 16 from bcc import BPF 17 import argparse 18 import ctypes as ct 19 20 # arguments 21 examples = """examples: 22 ./statsnoop # trace all stat() syscalls 23 ./statsnoop -t # include timestamps 24 ./statsnoop -x # only show failed stats 25 ./statsnoop -p 181 # only trace PID 181 26 """ 27 parser = argparse.ArgumentParser( 28 description="Trace stat() syscalls", 29 formatter_class=argparse.RawDescriptionHelpFormatter, 30 epilog=examples) 31 parser.add_argument("-t", "--timestamp", action="store_true", 32 help="include timestamp on output") 33 parser.add_argument("-x", "--failed", action="store_true", 34 help="only show failed stats") 35 parser.add_argument("-p", "--pid", 36 help="trace this PID only") 37 parser.add_argument("--ebpf", action="store_true", 38 help=argparse.SUPPRESS) 39 args = parser.parse_args() 40 debug = 0 41 42 # define BPF program 43 bpf_text = """ 44 #include <uapi/linux/ptrace.h> 45 #include <uapi/linux/limits.h> 46 #include <linux/sched.h> 47 48 struct val_t { 49 const char *fname; 50 }; 51 52 struct data_t { 53 u32 pid; 54 u64 ts_ns; 55 int ret; 56 char comm[TASK_COMM_LEN]; 57 char fname[NAME_MAX]; 58 }; 59 60 BPF_HASH(args_filename, u32, const char *); 61 BPF_HASH(infotmp, u32, struct val_t); 62 BPF_PERF_OUTPUT(events); 63 64 int syscall__entry(struct pt_regs *ctx, const char __user *filename) 65 { 66 struct val_t val = {}; 67 u32 pid = bpf_get_current_pid_tgid(); 68 69 FILTER 70 val.fname = filename; 71 infotmp.update(&pid, &val); 72 73 return 0; 74 }; 75 76 int trace_return(struct pt_regs *ctx) 77 { 78 u32 pid = bpf_get_current_pid_tgid(); 79 struct val_t *valp; 80 81 valp = infotmp.lookup(&pid); 82 if (valp == 0) { 83 // missed entry 84 return 0; 85 } 86 87 struct data_t data = {.pid = pid}; 88 bpf_probe_read(&data.fname, sizeof(data.fname), (void *)valp->fname); 89 bpf_get_current_comm(&data.comm, sizeof(data.comm)); 90 data.ts_ns = bpf_ktime_get_ns(); 91 data.ret = PT_REGS_RC(ctx); 92 93 events.perf_submit(ctx, &data, sizeof(data)); 94 infotmp.delete(&pid); 95 args_filename.delete(&pid); 96 97 return 0; 98 } 99 """ 100 if args.pid: 101 bpf_text = bpf_text.replace('FILTER', 102 'if (pid != %s) { return 0; }' % args.pid) 103 else: 104 bpf_text = bpf_text.replace('FILTER', '') 105 if debug or args.ebpf: 106 print(bpf_text) 107 if args.ebpf: 108 exit() 109 110 # initialize BPF 111 b = BPF(text=bpf_text) 112 113 # for POSIX compliance, all architectures implement these 114 # system calls but the name of the actual entry point may 115 # be different for which we must check if the entry points 116 # actually exist before attaching the probes 117 syscall_fnname = b.get_syscall_fnname("stat") 118 if BPF.ksymname(syscall_fnname) != -1: 119 b.attach_kprobe(event=syscall_fnname, fn_name="syscall__entry") 120 b.attach_kretprobe(event=syscall_fnname, fn_name="trace_return") 121 122 syscall_fnname = b.get_syscall_fnname("statfs") 123 if BPF.ksymname(syscall_fnname) != -1: 124 b.attach_kprobe(event=syscall_fnname, fn_name="syscall__entry") 125 b.attach_kretprobe(event=syscall_fnname, fn_name="trace_return") 126 127 syscall_fnname = b.get_syscall_fnname("newstat") 128 if BPF.ksymname(syscall_fnname) != -1: 129 b.attach_kprobe(event=syscall_fnname, fn_name="syscall__entry") 130 b.attach_kretprobe(event=syscall_fnname, fn_name="trace_return") 131 132 TASK_COMM_LEN = 16 # linux/sched.h 133 NAME_MAX = 255 # linux/limits.h 134 135 class Data(ct.Structure): 136 _fields_ = [ 137 ("pid", ct.c_ulonglong), 138 ("ts_ns", ct.c_ulonglong), 139 ("ret", ct.c_int), 140 ("comm", ct.c_char * TASK_COMM_LEN), 141 ("fname", ct.c_char * NAME_MAX) 142 ] 143 144 start_ts = 0 145 prev_ts = 0 146 delta = 0 147 148 # header 149 if args.timestamp: 150 print("%-14s" % ("TIME(s)"), end="") 151 print("%-6s %-16s %4s %3s %s" % ("PID", "COMM", "FD", "ERR", "PATH")) 152 153 # process event 154 def print_event(cpu, data, size): 155 event = ct.cast(data, ct.POINTER(Data)).contents 156 global start_ts 157 global prev_ts 158 global delta 159 global cont 160 161 # split return value into FD and errno columns 162 if event.ret >= 0: 163 fd_s = event.ret 164 err = 0 165 else: 166 fd_s = -1 167 err = - event.ret 168 169 if start_ts == 0: 170 start_ts = event.ts_ns 171 172 if args.timestamp: 173 print("%-14.9f" % (float(event.ts_ns - start_ts) / 1000000000), end="") 174 175 print("%-6d %-16s %4d %3d %s" % (event.pid, 176 event.comm.decode('utf-8', 'replace'), fd_s, err, 177 event.fname.decode('utf-8', 'replace'))) 178 179 # loop with callback to print_event 180 b["events"].open_perf_buffer(print_event, page_cnt=64) 181 while 1: 182 b.perf_buffer_poll() 183