diff --git a/man/man8/filelife.8 b/man/man8/filelife.8 index b57e21a3ac48..1047319155e3 100644 --- a/man/man8/filelife.8 +++ b/man/man8/filelife.8 @@ -13,6 +13,10 @@ This works by tracing the kernel vfs_create() and vfs_delete() functions using dynamic tracing, and will need updating to match any changes to these functions. +This makes use of a Linux 4.5 feature (bpf_perf_event_output()); +for kernels older than 4.5, see the version under tools/old, +which uses an older mechanism. + Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bcc. diff --git a/tools/filelife.py b/tools/filelife.py index 075be087d37d..b5145c57b64b 100755 --- a/tools/filelife.py +++ b/tools/filelife.py @@ -15,11 +15,13 @@ # Licensed under the Apache License, Version 2.0 (the "License") # # 08-Feb-2015 Brendan Gregg Created this. +# 17-Feb-2016 Allan McAleavy updated for BPF_PERF_OUTPUT from __future__ import print_function from bcc import BPF import argparse from time import strftime +import ctypes as ct # arguments examples = """examples: @@ -39,8 +41,17 @@ bpf_text = """ #include #include +#include + +struct data_t { + u32 pid; + u64 delta; + char comm[TASK_COMM_LEN]; + char fname[DNAME_INLINE_LEN]; +}; BPF_HASH(birth, struct dentry *); +BPF_PERF_OUTPUT(events); // trace file creation time int trace_create(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) @@ -57,7 +68,9 @@ // trace file deletion and output details int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) { + struct data_t data = {}; u32 pid = bpf_get_current_pid_tgid(); + FILTER u64 *tsp, delta; @@ -65,17 +78,36 @@ if (tsp == 0) { return 0; // missed create } + delta = (bpf_ktime_get_ns() - *tsp) / 1000000; birth.delete(&dentry); if (dentry->d_iname[0] == 0) return 0; - bpf_trace_printk("%d %s\\n", delta, dentry->d_iname); + if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) { + data.pid = pid; + data.delta = delta; + bpf_probe_read(&data.fname, sizeof(data.fname), dentry->d_iname); + } + + events.perf_submit(ctx, &data, sizeof(data)); return 0; } """ + +TASK_COMM_LEN = 16 # linux/sched.h +DNAME_INLINE_LEN = 255 # linux/dcache.h + +class Data(ct.Structure): + _fields_ = [ + ("pid", ct.c_ulonglong), + ("delta", ct.c_ulonglong), + ("comm", ct.c_char * TASK_COMM_LEN), + ("fname", ct.c_char * DNAME_INLINE_LEN) + ] + if args.pid: bpf_text = bpf_text.replace('FILTER', 'if (pid != %s) { return 0; }' % args.pid) @@ -92,13 +124,12 @@ # header print("%-8s %-6s %-16s %-7s %s" % ("TIME", "PID", "COMM", "AGE(s)", "FILE")) -start_ts = 0 +# process event +def print_event(cpu, data, size): + event = ct.cast(data, ct.POINTER(Data)).contents + print("%-8s %-6d %-16s %-7.2f %s" % (strftime("%H:%M:%S"), event.pid, + event.comm, float(event.delta) / 1000, event.fname)) -# format output +b["events"].open_perf_buffer(print_event) while 1: - (task, pid, cpu, flags, ts, msg) = b.trace_fields() - (delta, filename) = msg.split(" ", 1) - - # print columns - print("%-8s %-6d %-16s %-7.2f %s" % (strftime("%H:%M:%S"), pid, task, - float(delta) / 1000, filename)) + b.kprobe_poll() diff --git a/tools/old/filelife.py b/tools/old/filelife.py new file mode 100755 index 000000000000..075be087d37d --- /dev/null +++ b/tools/old/filelife.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# @lint-avoid-python-3-compatibility-imports +# +# filelife Trace the lifespan of short-lived files. +# For Linux, uses BCC, eBPF. Embedded C. +# +# This traces the creation and deletion of files, providing information +# on who deleted the file, the file age, and the file name. The intent is to +# provide information on short-lived files, for debugging or performance +# analysis. +# +# USAGE: filelife [-h] [-p PID] +# +# Copyright 2016 Netflix, Inc. +# Licensed under the Apache License, Version 2.0 (the "License") +# +# 08-Feb-2015 Brendan Gregg Created this. + +from __future__ import print_function +from bcc import BPF +import argparse +from time import strftime + +# arguments +examples = """examples: + ./filelife # trace all stat() syscalls + ./filelife -p 181 # only trace PID 181 +""" +parser = argparse.ArgumentParser( + description="Trace stat() syscalls", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=examples) +parser.add_argument("-p", "--pid", + help="trace this PID only") +args = parser.parse_args() +debug = 0 + +# define BPF program +bpf_text = """ +#include +#include + +BPF_HASH(birth, struct dentry *); + +// trace file creation time +int trace_create(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) +{ + u32 pid = bpf_get_current_pid_tgid(); + FILTER + + u64 ts = bpf_ktime_get_ns(); + birth.update(&dentry, &ts); + + return 0; +}; + +// trace file deletion and output details +int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) +{ + u32 pid = bpf_get_current_pid_tgid(); + FILTER + + u64 *tsp, delta; + tsp = birth.lookup(&dentry); + if (tsp == 0) { + return 0; // missed create + } + delta = (bpf_ktime_get_ns() - *tsp) / 1000000; + birth.delete(&dentry); + + if (dentry->d_iname[0] == 0) + return 0; + + bpf_trace_printk("%d %s\\n", delta, dentry->d_iname); + + return 0; +} +""" +if args.pid: + bpf_text = bpf_text.replace('FILTER', + 'if (pid != %s) { return 0; }' % args.pid) +else: + bpf_text = bpf_text.replace('FILTER', '') +if debug: + print(bpf_text) + +# initialize BPF +b = BPF(text=bpf_text) +b.attach_kprobe(event="vfs_create", fn_name="trace_create") +b.attach_kprobe(event="vfs_unlink", fn_name="trace_unlink") + +# header +print("%-8s %-6s %-16s %-7s %s" % ("TIME", "PID", "COMM", "AGE(s)", "FILE")) + +start_ts = 0 + +# format output +while 1: + (task, pid, cpu, flags, ts, msg) = b.trace_fields() + (delta, filename) = msg.split(" ", 1) + + # print columns + print("%-8s %-6d %-16s %-7.2f %s" % (strftime("%H:%M:%S"), pid, task, + float(delta) / 1000, filename))