• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/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
20from __future__ import print_function
21from bcc import BPF
22import argparse
23from time import strftime
24import ctypes as ct
25
26# arguments
27examples = """examples:
28    ./filelife           # trace all stat() syscalls
29    ./filelife -p 181    # only trace PID 181
30"""
31parser = argparse.ArgumentParser(
32    description="Trace stat() syscalls",
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
58// trace file creation time
59int trace_create(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry)
60{
61    u32 pid = bpf_get_current_pid_tgid();
62    FILTER
63
64    u64 ts = bpf_ktime_get_ns();
65    birth.update(&dentry, &ts);
66
67    return 0;
68};
69
70// trace file deletion and output details
71int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry)
72{
73    struct data_t data = {};
74    u32 pid = bpf_get_current_pid_tgid();
75
76    FILTER
77
78    u64 *tsp, delta;
79    tsp = birth.lookup(&dentry);
80    if (tsp == 0) {
81        return 0;   // missed create
82    }
83
84    delta = (bpf_ktime_get_ns() - *tsp) / 1000000;
85    birth.delete(&dentry);
86
87    struct qstr d_name = dentry->d_name;
88    if (d_name.len == 0)
89        return 0;
90
91    if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) {
92        data.pid = pid;
93        data.delta = delta;
94        bpf_probe_read(&data.fname, sizeof(data.fname), d_name.name);
95    }
96
97    events.perf_submit(ctx, &data, sizeof(data));
98
99    return 0;
100}
101"""
102
103TASK_COMM_LEN = 16            # linux/sched.h
104DNAME_INLINE_LEN = 255        # linux/dcache.h
105
106class Data(ct.Structure):
107    _fields_ = [
108        ("pid", ct.c_uint),
109        ("delta", ct.c_ulonglong),
110        ("comm", ct.c_char * TASK_COMM_LEN),
111        ("fname", ct.c_char * DNAME_INLINE_LEN)
112    ]
113
114if args.pid:
115    bpf_text = bpf_text.replace('FILTER',
116        'if (pid != %s) { return 0; }' % args.pid)
117else:
118    bpf_text = bpf_text.replace('FILTER', '')
119if debug or args.ebpf:
120    print(bpf_text)
121    if args.ebpf:
122        exit()
123
124# initialize BPF
125b = BPF(text=bpf_text)
126b.attach_kprobe(event="vfs_create", fn_name="trace_create")
127# newer kernels (say, 4.8) may don't fire vfs_create, so record (or overwrite)
128# the timestamp in security_inode_create():
129b.attach_kprobe(event="security_inode_create", fn_name="trace_create")
130b.attach_kprobe(event="vfs_unlink", fn_name="trace_unlink")
131
132# header
133print("%-8s %-6s %-16s %-7s %s" % ("TIME", "PID", "COMM", "AGE(s)", "FILE"))
134
135# process event
136def print_event(cpu, data, size):
137    event = ct.cast(data, ct.POINTER(Data)).contents
138    print("%-8s %-6d %-16s %-7.2f %s" % (strftime("%H:%M:%S"), event.pid,
139        event.comm.decode('utf-8', 'replace'), float(event.delta) / 1000,
140        event.fname.decode('utf-8', 'replace')))
141
142b["events"].open_perf_buffer(print_event)
143while 1:
144    b.perf_buffer_poll()
145