xref: /aosp_15_r20/external/bcc/tools/statsnoop.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1#!/usr/bin/env 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# 29-Nov-2022   Rocky Xing      Added stat() variants.
15
16from __future__ import print_function
17from bcc import BPF
18import argparse
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(infotmp, u32, struct val_t);
61BPF_PERF_OUTPUT(events);
62
63static int trace_entry(struct pt_regs *ctx, const char __user *filename)
64{
65    struct val_t val = {};
66    u64 pid_tgid = bpf_get_current_pid_tgid();
67    u32 pid = pid_tgid >> 32;
68    u32 tid = (u32)pid_tgid;
69
70    FILTER
71    val.fname = filename;
72    infotmp.update(&tid, &val);
73
74    return 0;
75};
76
77int syscall__stat_entry(struct pt_regs *ctx, const char __user *filename)
78{
79    return trace_entry(ctx, filename);
80}
81
82int syscall__statx_entry(struct pt_regs *ctx, int dfd, const char __user *filename)
83{
84    return trace_entry(ctx, filename);
85}
86
87int trace_return(struct pt_regs *ctx)
88{
89    u64 pid_tgid = bpf_get_current_pid_tgid();
90    u32 tid = (u32)pid_tgid;
91    struct val_t *valp;
92
93    valp = infotmp.lookup(&tid);
94    if (valp == 0) {
95        // missed entry
96        return 0;
97    }
98
99    struct data_t data = {.pid = pid_tgid >> 32};
100    bpf_probe_read_user(&data.fname, sizeof(data.fname), (void *)valp->fname);
101    bpf_get_current_comm(&data.comm, sizeof(data.comm));
102    data.ts_ns = bpf_ktime_get_ns();
103    data.ret = PT_REGS_RC(ctx);
104
105    events.perf_submit(ctx, &data, sizeof(data));
106    infotmp.delete(&tid);
107
108    return 0;
109}
110"""
111if args.pid:
112    bpf_text = bpf_text.replace('FILTER',
113        'if (pid != %s) { return 0; }' % args.pid)
114else:
115    bpf_text = bpf_text.replace('FILTER', '')
116if debug or args.ebpf:
117    print(bpf_text)
118    if args.ebpf:
119        exit()
120
121# initialize BPF
122b = BPF(text=bpf_text)
123
124# for POSIX compliance, all architectures implement these
125# system calls but the name of the actual entry point may
126# be different for which we must check if the entry points
127# actually exist before attaching the probes
128def try_attach_syscall_probes(syscall):
129    syscall_fnname = b.get_syscall_fnname(syscall)
130    if BPF.ksymname(syscall_fnname) != -1:
131        if syscall in ["statx", "fstatat64", "newfstatat"]:
132            b.attach_kprobe(event=syscall_fnname, fn_name="syscall__statx_entry")
133        else:
134            b.attach_kprobe(event=syscall_fnname, fn_name="syscall__stat_entry")
135        b.attach_kretprobe(event=syscall_fnname, fn_name="trace_return")
136
137try_attach_syscall_probes("stat")
138try_attach_syscall_probes("statx")
139try_attach_syscall_probes("statfs")
140try_attach_syscall_probes("newstat")
141try_attach_syscall_probes("newlstat")
142try_attach_syscall_probes("fstatat64")
143try_attach_syscall_probes("newfstatat")
144
145start_ts = 0
146prev_ts = 0
147delta = 0
148
149# header
150if args.timestamp:
151    print("%-14s" % ("TIME(s)"), end="")
152print("%-7s %-16s %4s %3s %s" % ("PID", "COMM", "FD", "ERR", "PATH"))
153
154# process event
155def print_event(cpu, data, size):
156    event = b["events"].event(data)
157    global start_ts
158    global prev_ts
159    global delta
160    global cont
161
162    # split return value into FD and errno columns
163    if event.ret >= 0:
164        if args.failed:
165            return
166        fd_s = event.ret
167        err = 0
168    else:
169        fd_s = -1
170        err = - event.ret
171
172    if start_ts == 0:
173        start_ts = event.ts_ns
174
175    if args.timestamp:
176        print("%-14.9f" % (float(event.ts_ns - start_ts) / 1000000000), end="")
177
178    print("%-7d %-16s %4d %3d %s" % (event.pid,
179        event.comm.decode('utf-8', 'replace'), fd_s, err,
180        event.fname.decode('utf-8', 'replace')))
181
182# loop with callback to print_event
183b["events"].open_perf_buffer(print_event, page_cnt=64)
184while 1:
185    try:
186        b.perf_buffer_poll()
187    except KeyboardInterrupt:
188        exit()
189