• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15from __future__ import print_function
16from bcc import BPF
17import argparse
18import ctypes as ct
19
20# arguments
21examples = """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"""
27parser = argparse.ArgumentParser(
28    description="Trace stat() syscalls",
29    formatter_class=argparse.RawDescriptionHelpFormatter,
30    epilog=examples)
31parser.add_argument("-t", "--timestamp", action="store_true",
32    help="include timestamp on output")
33parser.add_argument("-x", "--failed", action="store_true",
34    help="only show failed stats")
35parser.add_argument("-p", "--pid",
36    help="trace this PID only")
37parser.add_argument("--ebpf", action="store_true",
38    help=argparse.SUPPRESS)
39args = parser.parse_args()
40debug = 0
41
42# define BPF program
43bpf_text = """
44#include <uapi/linux/ptrace.h>
45#include <uapi/linux/limits.h>
46#include <linux/sched.h>
47
48struct val_t {
49    const char *fname;
50};
51
52struct 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
60BPF_HASH(args_filename, u32, const char *);
61BPF_HASH(infotmp, u32, struct val_t);
62BPF_PERF_OUTPUT(events);
63
64int 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
76int 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"""
100if args.pid:
101    bpf_text = bpf_text.replace('FILTER',
102        'if (pid != %s) { return 0; }' % args.pid)
103else:
104    bpf_text = bpf_text.replace('FILTER', '')
105if debug or args.ebpf:
106    print(bpf_text)
107    if args.ebpf:
108        exit()
109
110# initialize BPF
111b = 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
117syscall_fnname = b.get_syscall_fnname("stat")
118if 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
122syscall_fnname = b.get_syscall_fnname("statfs")
123if 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
127syscall_fnname = b.get_syscall_fnname("newstat")
128if 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
132TASK_COMM_LEN = 16    # linux/sched.h
133NAME_MAX = 255        # linux/limits.h
134
135class 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
144start_ts = 0
145prev_ts = 0
146delta = 0
147
148# header
149if args.timestamp:
150    print("%-14s" % ("TIME(s)"), end="")
151print("%-6s %-16s %4s %3s %s" % ("PID", "COMM", "FD", "ERR", "PATH"))
152
153# process event
154def 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
180b["events"].open_perf_buffer(print_event, page_cnt=64)
181while 1:
182    b.perf_buffer_poll()
183