blob: 86776657ad263a4a79541a75efae0d9221718097 [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)
Mark Drayton74347312016-08-25 20:46:35 +010035parser.add_argument("-a", "--all-files", action="store_true",
36 help="include non-regular file types (sockets, FIFOs, etc)")
Brendan Gregg08c29812016-02-09 00:36:43 -080037parser.add_argument("-C", "--noclear", action="store_true",
38 help="don't clear the screen")
39parser.add_argument("-r", "--maxrows", default=20,
40 help="maximum rows to print, default 20")
Daniel Neitercaa38c52017-03-01 17:21:25 -080041parser.add_argument("-s", "--sort", default="rbytes",
42 choices=["reads", "writes", "rbytes", "wbytes"],
43 help="sort column, default rbytes")
Mark Drayton74347312016-08-25 20:46:35 +010044parser.add_argument("-p", "--pid", type=int, metavar="PID", dest="tgid",
Brendan Gregg08c29812016-02-09 00:36:43 -080045 help="trace this PID only")
46parser.add_argument("interval", nargs="?", default=1,
47 help="output interval, in seconds")
48parser.add_argument("count", nargs="?", default=99999999,
49 help="number of outputs")
Nathan Scottcf0792f2018-02-02 16:56:50 +110050parser.add_argument("--ebpf", action="store_true",
51 help=argparse.SUPPRESS)
Brendan Gregg08c29812016-02-09 00:36:43 -080052args = parser.parse_args()
53interval = int(args.interval)
54countdown = int(args.count)
55maxrows = int(args.maxrows)
56clear = not int(args.noclear)
57debug = 0
58
59# linux stats
60loadavg = "/proc/loadavg"
61
62# signal handler
63def signal_ignore(signal, frame):
64 print()
65
66# define BPF program
67bpf_text = """
68#include <uapi/linux/ptrace.h>
69#include <linux/blkdev.h>
70
Brendan Gregg08c29812016-02-09 00:36:43 -080071// the key for the output summary
72struct info_t {
73 u32 pid;
Mark Drayton74347312016-08-25 20:46:35 +010074 u32 name_len;
75 char comm[TASK_COMM_LEN];
76 // de->d_name.name may point to de->d_iname so limit len accordingly
77 char name[DNAME_INLINE_LEN];
Brendan Gregg08c29812016-02-09 00:36:43 -080078 char type;
79};
80
81// the value of the output summary
82struct val_t {
83 u64 reads;
84 u64 writes;
85 u64 rbytes;
86 u64 wbytes;
87};
88
89BPF_HASH(counts, struct info_t, struct val_t);
90
91static int do_entry(struct pt_regs *ctx, struct file *file,
92 char __user *buf, size_t count, int is_read)
93{
Mark Drayton74347312016-08-25 20:46:35 +010094 u32 tgid = bpf_get_current_pid_tgid() >> 32;
95 if (TGID_FILTER)
Brendan Gregg08c29812016-02-09 00:36:43 -080096 return 0;
97
Mark Drayton74347312016-08-25 20:46:35 +010098 u32 pid = bpf_get_current_pid_tgid();
99
Brendan Gregg08c29812016-02-09 00:36:43 -0800100 // skip I/O lacking a filename
101 struct dentry *de = file->f_path.dentry;
Mark Drayton74347312016-08-25 20:46:35 +0100102 int mode = file->f_inode->i_mode;
103 if (de->d_name.len == 0 || TYPE_FILTER)
Brendan Gregg08c29812016-02-09 00:36:43 -0800104 return 0;
105
106 // store counts and sizes by pid & file
107 struct info_t info = {.pid = pid};
Mark Drayton74347312016-08-25 20:46:35 +0100108 bpf_get_current_comm(&info.comm, sizeof(info.comm));
109 info.name_len = de->d_name.len;
110 bpf_probe_read(&info.name, sizeof(info.name), (void *)de->d_name.name);
Brendan Gregg08c29812016-02-09 00:36:43 -0800111 if (S_ISREG(mode)) {
112 info.type = 'R';
113 } else if (S_ISSOCK(mode)) {
114 info.type = 'S';
115 } else {
116 info.type = 'O';
117 }
118
119 struct val_t *valp, zero = {};
120 valp = counts.lookup_or_init(&info, &zero);
121 if (is_read) {
122 valp->reads++;
123 valp->rbytes += count;
124 } else {
125 valp->writes++;
126 valp->wbytes += count;
127 }
128
129 return 0;
130}
131
132int trace_read_entry(struct pt_regs *ctx, struct file *file,
133 char __user *buf, size_t count)
134{
135 return do_entry(ctx, file, buf, count, 1);
136}
137
138int trace_write_entry(struct pt_regs *ctx, struct file *file,
139 char __user *buf, size_t count)
140{
141 return do_entry(ctx, file, buf, count, 0);
142}
143
144"""
Mark Drayton74347312016-08-25 20:46:35 +0100145if args.tgid:
146 bpf_text = bpf_text.replace('TGID_FILTER', 'tgid != %d' % args.tgid)
Brendan Gregg08c29812016-02-09 00:36:43 -0800147else:
Mark Drayton74347312016-08-25 20:46:35 +0100148 bpf_text = bpf_text.replace('TGID_FILTER', '0')
149if args.all_files:
150 bpf_text = bpf_text.replace('TYPE_FILTER', '0')
151else:
152 bpf_text = bpf_text.replace('TYPE_FILTER', '!S_ISREG(mode)')
153
Nathan Scottcf0792f2018-02-02 16:56:50 +1100154if debug or args.ebpf:
Brendan Gregg08c29812016-02-09 00:36:43 -0800155 print(bpf_text)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100156 if args.ebpf:
157 exit()
Brendan Gregg08c29812016-02-09 00:36:43 -0800158
159# initialize BPF
160b = BPF(text=bpf_text)
Jazel Canseco6d818b62018-04-13 11:39:56 -0700161b.attach_kprobe(event="vfs_read", fn_name="trace_read_entry")
162b.attach_kprobe(event="vfs_write", fn_name="trace_write_entry")
Mark Drayton74347312016-08-25 20:46:35 +0100163
164DNAME_INLINE_LEN = 32 # linux/dcache.h
Brendan Gregg08c29812016-02-09 00:36:43 -0800165
166print('Tracing... Output every %d secs. Hit Ctrl-C to end' % interval)
167
168# output
169exiting = 0
170while 1:
171 try:
172 sleep(interval)
173 except KeyboardInterrupt:
174 exiting = 1
175
176 # header
177 if clear:
178 call("clear")
179 else:
180 print()
181 with open(loadavg) as stats:
182 print("%-8s loadavg: %s" % (strftime("%H:%M:%S"), stats.read()))
Mark Drayton74347312016-08-25 20:46:35 +0100183 print("%-6s %-16s %-6s %-6s %-7s %-7s %1s %s" % ("TID", "COMM",
Brendan Gregg08c29812016-02-09 00:36:43 -0800184 "READS", "WRITES", "R_Kb", "W_Kb", "T", "FILE"))
185
Mark Drayton74347312016-08-25 20:46:35 +0100186 # by-TID output
Brendan Gregg08c29812016-02-09 00:36:43 -0800187 counts = b.get_table("counts")
188 line = 0
189 for k, v in reversed(sorted(counts.items(),
Daniel Neitercaa38c52017-03-01 17:21:25 -0800190 key=lambda counts:
191 getattr(counts[1], args.sort))):
Rafael Fonseca29392652017-02-06 17:07:28 +0100192 name = k.name.decode()
Mark Drayton74347312016-08-25 20:46:35 +0100193 if k.name_len > DNAME_INLINE_LEN:
194 name = name[:-3] + "..."
Brendan Gregg08c29812016-02-09 00:36:43 -0800195
196 # print line
Rafael Fonseca29392652017-02-06 17:07:28 +0100197 print("%-6d %-16s %-6d %-6d %-7d %-7d %1s %s" % (k.pid,
198 k.comm.decode(), v.reads, v.writes, v.rbytes / 1024,
199 v.wbytes / 1024, k.type.decode(), name))
Brendan Gregg08c29812016-02-09 00:36:43 -0800200
201 line += 1
202 if line >= maxrows:
203 break
204 counts.clear()
205
206 countdown -= 1
207 if exiting or countdown == 0:
208 print("Detaching...")
209 exit()