blob: 047dd8c254c5912cb3c8cc78b6462962a8f67633 [file] [log] [blame]
Brendan Gregg08c29812016-02-09 00:36:43 -08001#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
4# filetop file reads and writes by process.
5# For Linux, uses BCC, eBPF.
6#
7# USAGE: filetop.py [-h] [-C] [-r MAXROWS] [interval] [count]
8#
9# This uses in-kernel eBPF maps to store per process summaries for efficiency.
10#
11# Copyright 2016 Netflix, Inc.
12# Licensed under the Apache License, Version 2.0 (the "License")
13#
14# 06-Feb-2016 Brendan Gregg Created this.
15
16from __future__ import print_function
17from bcc import BPF
18from time import sleep, strftime
19import argparse
20import signal
21from subprocess import call
22
23# arguments
24examples = """examples:
25 ./filetop # file I/O top, 1 second refresh
26 ./filetop -C # don't clear the screen
27 ./filetop -p 181 # PID 181 only
28 ./filetop 5 # 5 second summaries
29 ./filetop 5 10 # 5 second summaries, 10 times only
30"""
31parser = argparse.ArgumentParser(
32 description="File reads and writes by process",
33 formatter_class=argparse.RawDescriptionHelpFormatter,
34 epilog=examples)
35parser.add_argument("-C", "--noclear", action="store_true",
36 help="don't clear the screen")
37parser.add_argument("-r", "--maxrows", default=20,
38 help="maximum rows to print, default 20")
39parser.add_argument("-p", "--pid",
40 help="trace this PID only")
41parser.add_argument("interval", nargs="?", default=1,
42 help="output interval, in seconds")
43parser.add_argument("count", nargs="?", default=99999999,
44 help="number of outputs")
45args = parser.parse_args()
46interval = int(args.interval)
47countdown = int(args.count)
48maxrows = int(args.maxrows)
49clear = not int(args.noclear)
50debug = 0
51
52# linux stats
53loadavg = "/proc/loadavg"
54
55# signal handler
56def signal_ignore(signal, frame):
57 print()
58
59# define BPF program
60bpf_text = """
61#include <uapi/linux/ptrace.h>
62#include <linux/blkdev.h>
63
64#define MAX_FILE_LEN 32
65
66// the key for the output summary
67struct info_t {
68 u32 pid;
69 char name[TASK_COMM_LEN];
70 char file[MAX_FILE_LEN];
71 char type;
72};
73
74// the value of the output summary
75struct val_t {
76 u64 reads;
77 u64 writes;
78 u64 rbytes;
79 u64 wbytes;
80};
81
82BPF_HASH(counts, struct info_t, struct val_t);
83
84static int do_entry(struct pt_regs *ctx, struct file *file,
85 char __user *buf, size_t count, int is_read)
86{
87 u32 pid;
88
89 pid = bpf_get_current_pid_tgid();
90 if (FILTER)
91 return 0;
92
93 // skip I/O lacking a filename
94 struct dentry *de = file->f_path.dentry;
95 if (de->d_iname[0] == 0)
96 return 0;
97
98 // store counts and sizes by pid & file
99 struct info_t info = {.pid = pid};
100 bpf_get_current_comm(&info.name, sizeof(info.name));
101 __builtin_memcpy(&info.file, de->d_iname, sizeof(info.file));
102 int mode = file->f_inode->i_mode;
103 if (S_ISREG(mode)) {
104 info.type = 'R';
105 } else if (S_ISSOCK(mode)) {
106 info.type = 'S';
107 } else {
108 info.type = 'O';
109 }
110
111 struct val_t *valp, zero = {};
112 valp = counts.lookup_or_init(&info, &zero);
113 if (is_read) {
114 valp->reads++;
115 valp->rbytes += count;
116 } else {
117 valp->writes++;
118 valp->wbytes += count;
119 }
120
121 return 0;
122}
123
124int trace_read_entry(struct pt_regs *ctx, struct file *file,
125 char __user *buf, size_t count)
126{
127 return do_entry(ctx, file, buf, count, 1);
128}
129
130int trace_write_entry(struct pt_regs *ctx, struct file *file,
131 char __user *buf, size_t count)
132{
133 return do_entry(ctx, file, buf, count, 0);
134}
135
136"""
137if args.pid:
138 bpf_text = bpf_text.replace('FILTER', 'pid != %s' % args.pid)
139else:
140 bpf_text = bpf_text.replace('FILTER', '0')
141if debug:
142 print(bpf_text)
143
144# initialize BPF
145b = BPF(text=bpf_text)
146b.attach_kprobe(event="__vfs_read", fn_name="trace_read_entry")
147b.attach_kprobe(event="__vfs_write", fn_name="trace_write_entry")
148
149print('Tracing... Output every %d secs. Hit Ctrl-C to end' % interval)
150
151# output
152exiting = 0
153while 1:
154 try:
155 sleep(interval)
156 except KeyboardInterrupt:
157 exiting = 1
158
159 # header
160 if clear:
161 call("clear")
162 else:
163 print()
164 with open(loadavg) as stats:
165 print("%-8s loadavg: %s" % (strftime("%H:%M:%S"), stats.read()))
166 print("%-6s %-16s %-6s %-6s %-7s %-7s %1s %s" % ("PID", "COMM",
167 "READS", "WRITES", "R_Kb", "W_Kb", "T", "FILE"))
168
169 # by-PID output
170 counts = b.get_table("counts")
171 line = 0
172 for k, v in reversed(sorted(counts.items(),
173 key=lambda counts: counts[1].rbytes)):
174
175 # print line
176 print("%-6d %-16s %-6d %-6d %-7d %-7d %1s %s" % (k.pid, k.name,
177 v.reads, v.writes, v.rbytes / 1024, v.wbytes / 1024, k.type,
178 k.file))
179
180 line += 1
181 if line >= maxrows:
182 break
183 counts.clear()
184
185 countdown -= 1
186 if exiting or countdown == 0:
187 print("Detaching...")
188 exit()