xref: /aosp_15_r20/external/bcc/tools/filelife.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1#!/usr/bin/env python
2# @lint-avoid-python-3-compatibility-imports
3#
4# filelife    Trace the lifespan of short-lived files.
5#             For Linux, uses BCC, eBPF. Embedded C.
6#
7# This traces the creation and deletion of files, providing information
8# on who deleted the file, the file age, and the file name. The intent is to
9# provide information on short-lived files, for debugging or performance
10# analysis.
11#
12# USAGE: filelife [-h] [-p PID]
13#
14# Copyright 2016 Netflix, Inc.
15# Licensed under the Apache License, Version 2.0 (the "License")
16#
17# 08-Feb-2015   Brendan Gregg   Created this.
18# 17-Feb-2016   Allan McAleavy updated for BPF_PERF_OUTPUT
19# 13-Nov-2022   Rong Tao        Check btf struct field for CO-RE and add vfs_open()
20
21from __future__ import print_function
22from bcc import BPF
23import argparse
24from time import strftime
25
26# arguments
27examples = """examples:
28    ./filelife           # trace lifecycle of file(create->remove)
29    ./filelife -p 181    # only trace PID 181
30"""
31parser = argparse.ArgumentParser(
32    description="Trace lifecycle of file",
33    formatter_class=argparse.RawDescriptionHelpFormatter,
34    epilog=examples)
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 <linux/fs.h>
46#include <linux/sched.h>
47
48struct data_t {
49    u32 pid;
50    u64 delta;
51    char comm[TASK_COMM_LEN];
52    char fname[DNAME_INLINE_LEN];
53};
54
55BPF_HASH(birth, struct dentry *);
56BPF_PERF_OUTPUT(events);
57
58static int probe_dentry(struct pt_regs *ctx, struct dentry *dentry)
59{
60    u32 pid = bpf_get_current_pid_tgid() >> 32;
61    FILTER
62
63    u64 ts = bpf_ktime_get_ns();
64    birth.update(&dentry, &ts);
65
66    return 0;
67}
68
69// trace file creation time
70TRACE_CREATE_FUNC
71{
72    return probe_dentry(ctx, dentry);
73};
74
75// trace file security_inode_create time
76int trace_security_inode_create(struct pt_regs *ctx, struct inode *dir,
77        struct dentry *dentry)
78{
79    return probe_dentry(ctx, dentry);
80};
81
82// trace file open time
83int trace_open(struct pt_regs *ctx, struct path *path, struct file *file)
84{
85    struct dentry *dentry = path->dentry;
86
87    if (!(file->f_mode & FMODE_CREATED)) {
88        return 0;
89    }
90
91    return probe_dentry(ctx, dentry);
92};
93
94// trace file deletion and output details
95TRACE_UNLINK_FUNC
96{
97    struct data_t data = {};
98    u32 pid = bpf_get_current_pid_tgid() >> 32;
99
100    FILTER
101
102    u64 *tsp, delta;
103    tsp = birth.lookup(&dentry);
104    if (tsp == 0) {
105        return 0;   // missed create
106    }
107
108    delta = (bpf_ktime_get_ns() - *tsp) / 1000000;
109    birth.delete(&dentry);
110
111    struct qstr d_name = dentry->d_name;
112    if (d_name.len == 0)
113        return 0;
114
115    if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) {
116        data.pid = pid;
117        data.delta = delta;
118        bpf_probe_read_kernel(&data.fname, sizeof(data.fname), d_name.name);
119    }
120
121    events.perf_submit(ctx, &data, sizeof(data));
122
123    return 0;
124}
125"""
126
127trace_create_text_1="""
128int trace_create(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry)
129"""
130trace_create_text_2="""
131int trace_create(struct pt_regs *ctx, struct user_namespace *mnt_userns,
132        struct inode *dir, struct dentry *dentry)
133"""
134trace_create_text_3="""
135int trace_create(struct pt_regs *ctx, struct mnt_idmap *idmap,
136        struct inode *dir, struct dentry *dentry)
137"""
138
139trace_unlink_text_1="""
140int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry)
141"""
142trace_unlink_text_2="""
143int trace_unlink(struct pt_regs *ctx, struct user_namespace *mnt_userns,
144        struct inode *dir, struct dentry *dentry)
145"""
146trace_unlink_text_3="""
147int trace_unlink(struct pt_regs *ctx, struct mnt_idmap *idmap,
148        struct inode *dir, struct dentry *dentry)
149"""
150
151if args.pid:
152    bpf_text = bpf_text.replace('FILTER',
153        'if (pid != %s) { return 0; }' % args.pid)
154else:
155    bpf_text = bpf_text.replace('FILTER', '')
156if debug or args.ebpf:
157    print(bpf_text)
158    if args.ebpf:
159        exit()
160
161if BPF.kernel_struct_has_field(b'renamedata', b'new_mnt_idmap') == 1:
162    bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_3)
163    bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_3)
164elif BPF.kernel_struct_has_field(b'renamedata', b'old_mnt_userns') == 1:
165    bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_2)
166    bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_2)
167else:
168    bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_1)
169    bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_1)
170
171# initialize BPF
172b = BPF(text=bpf_text)
173b.attach_kprobe(event="vfs_create", fn_name="trace_create")
174# newer kernels may don't fire vfs_create, call vfs_open instead:
175b.attach_kprobe(event="vfs_open", fn_name="trace_open")
176# newer kernels (say, 4.8) may don't fire vfs_create, so record (or overwrite)
177# the timestamp in security_inode_create():
178if BPF.get_kprobe_functions(b"security_inode_create"):
179    b.attach_kprobe(event="security_inode_create", fn_name="trace_security_inode_create")
180b.attach_kprobe(event="vfs_unlink", fn_name="trace_unlink")
181
182# header
183print("%-8s %-7s %-16s %-7s %s" % ("TIME", "PID", "COMM", "AGE(s)", "FILE"))
184
185# process event
186def print_event(cpu, data, size):
187    event = b["events"].event(data)
188    print("%-8s %-7d %-16s %-7.2f %s" % (strftime("%H:%M:%S"), event.pid,
189        event.comm.decode('utf-8', 'replace'), float(event.delta) / 1000,
190        event.fname.decode('utf-8', 'replace')))
191
192b["events"].open_perf_buffer(print_event)
193while 1:
194    try:
195        b.perf_buffer_poll()
196    except KeyboardInterrupt:
197        exit()
198