Home | History | Annotate | Download | only in tools
      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