blob: aec11a86c230d3b4f5701222eb36cd78331e3420 [file] [log] [blame]
Alexey Ivanovcc01a9c2019-01-16 09:50:46 -08001#!/usr/bin/python
Brendan Gregg08c29812016-02-09 00:36:43 -08002# @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
Brendan Gregg08c29812016-02-09 00:36:43 -080020from subprocess import call
21
22# arguments
23examples = """examples:
24 ./filetop # file I/O top, 1 second refresh
25 ./filetop -C # don't clear the screen
26 ./filetop -p 181 # PID 181 only
27 ./filetop 5 # 5 second summaries
28 ./filetop 5 10 # 5 second summaries, 10 times only
29"""
30parser = argparse.ArgumentParser(
31 description="File reads and writes by process",
32 formatter_class=argparse.RawDescriptionHelpFormatter,
33 epilog=examples)
Mark Drayton74347312016-08-25 20:46:35 +010034parser.add_argument("-a", "--all-files", action="store_true",
35 help="include non-regular file types (sockets, FIFOs, etc)")
Brendan Gregg08c29812016-02-09 00:36:43 -080036parser.add_argument("-C", "--noclear", action="store_true",
37 help="don't clear the screen")
38parser.add_argument("-r", "--maxrows", default=20,
39 help="maximum rows to print, default 20")
Joel Fernandes (Google)c2b371d2019-03-08 11:39:42 -050040parser.add_argument("-s", "--sort", default="all",
41 choices=["all", "reads", "writes", "rbytes", "wbytes"],
Felix Geisendörfer3106a822019-12-12 16:41:09 +080042 help="sort column, default all")
Mark Drayton74347312016-08-25 20:46:35 +010043parser.add_argument("-p", "--pid", type=int, metavar="PID", dest="tgid",
Brendan Gregg08c29812016-02-09 00:36:43 -080044 help="trace this PID only")
45parser.add_argument("interval", nargs="?", default=1,
46 help="output interval, in seconds")
47parser.add_argument("count", nargs="?", default=99999999,
48 help="number of outputs")
Nathan Scottcf0792f2018-02-02 16:56:50 +110049parser.add_argument("--ebpf", action="store_true",
50 help=argparse.SUPPRESS)
Brendan Gregg08c29812016-02-09 00:36:43 -080051args = parser.parse_args()
52interval = int(args.interval)
53countdown = int(args.count)
54maxrows = int(args.maxrows)
55clear = not int(args.noclear)
56debug = 0
57
58# linux stats
59loadavg = "/proc/loadavg"
60
Brendan Gregg08c29812016-02-09 00:36:43 -080061# define BPF program
62bpf_text = """
63#include <uapi/linux/ptrace.h>
64#include <linux/blkdev.h>
65
Brendan Gregg08c29812016-02-09 00:36:43 -080066// the key for the output summary
67struct info_t {
Hengqi Chend5673472021-07-28 23:49:11 +080068 unsigned long inode;
69 dev_t dev;
Hengqi Chen7db34882022-01-30 11:14:57 +080070 dev_t rdev;
Brendan Gregg08c29812016-02-09 00:36:43 -080071 u32 pid;
Mark Drayton74347312016-08-25 20:46:35 +010072 u32 name_len;
73 char comm[TASK_COMM_LEN];
74 // de->d_name.name may point to de->d_iname so limit len accordingly
75 char name[DNAME_INLINE_LEN];
Brendan Gregg08c29812016-02-09 00:36:43 -080076 char type;
77};
78
79// the value of the output summary
80struct val_t {
81 u64 reads;
82 u64 writes;
83 u64 rbytes;
84 u64 wbytes;
85};
86
87BPF_HASH(counts, struct info_t, struct val_t);
88
89static int do_entry(struct pt_regs *ctx, struct file *file,
90 char __user *buf, size_t count, int is_read)
91{
Mark Drayton74347312016-08-25 20:46:35 +010092 u32 tgid = bpf_get_current_pid_tgid() >> 32;
93 if (TGID_FILTER)
Brendan Gregg08c29812016-02-09 00:36:43 -080094 return 0;
95
Mark Drayton74347312016-08-25 20:46:35 +010096 u32 pid = bpf_get_current_pid_tgid();
97
Brendan Gregg08c29812016-02-09 00:36:43 -080098 // skip I/O lacking a filename
99 struct dentry *de = file->f_path.dentry;
Mark Drayton74347312016-08-25 20:46:35 +0100100 int mode = file->f_inode->i_mode;
Paul Chaignonf86f7e82018-06-14 02:20:03 +0200101 struct qstr d_name = de->d_name;
102 if (d_name.len == 0 || TYPE_FILTER)
Brendan Gregg08c29812016-02-09 00:36:43 -0800103 return 0;
104
105 // store counts and sizes by pid & file
Hengqi Chend5673472021-07-28 23:49:11 +0800106 struct info_t info = {
107 .pid = pid,
108 .inode = file->f_inode->i_ino,
Hengqi Chen7db34882022-01-30 11:14:57 +0800109 .dev = file->f_inode->i_sb->s_dev,
110 .rdev = file->f_inode->i_rdev,
Hengqi Chend5673472021-07-28 23:49:11 +0800111 };
Mark Drayton74347312016-08-25 20:46:35 +0100112 bpf_get_current_comm(&info.comm, sizeof(info.comm));
Paul Chaignonf86f7e82018-06-14 02:20:03 +0200113 info.name_len = d_name.len;
Sumanth Korikkar7f6066d2020-05-20 10:49:56 -0500114 bpf_probe_read_kernel(&info.name, sizeof(info.name), d_name.name);
Brendan Gregg08c29812016-02-09 00:36:43 -0800115 if (S_ISREG(mode)) {
116 info.type = 'R';
117 } else if (S_ISSOCK(mode)) {
118 info.type = 'S';
119 } else {
120 info.type = 'O';
121 }
122
123 struct val_t *valp, zero = {};
yonghong-song82f43022019-10-31 08:16:12 -0700124 valp = counts.lookup_or_try_init(&info, &zero);
Philip Gladstoneba64f032019-09-20 01:12:01 -0400125 if (valp) {
126 if (is_read) {
127 valp->reads++;
128 valp->rbytes += count;
129 } else {
130 valp->writes++;
131 valp->wbytes += count;
132 }
Brendan Gregg08c29812016-02-09 00:36:43 -0800133 }
134
135 return 0;
136}
137
138int trace_read_entry(struct pt_regs *ctx, struct file *file,
139 char __user *buf, size_t count)
140{
141 return do_entry(ctx, file, buf, count, 1);
142}
143
144int trace_write_entry(struct pt_regs *ctx, struct file *file,
145 char __user *buf, size_t count)
146{
147 return do_entry(ctx, file, buf, count, 0);
148}
149
150"""
Mark Drayton74347312016-08-25 20:46:35 +0100151if args.tgid:
152 bpf_text = bpf_text.replace('TGID_FILTER', 'tgid != %d' % args.tgid)
Brendan Gregg08c29812016-02-09 00:36:43 -0800153else:
Mark Drayton74347312016-08-25 20:46:35 +0100154 bpf_text = bpf_text.replace('TGID_FILTER', '0')
155if args.all_files:
156 bpf_text = bpf_text.replace('TYPE_FILTER', '0')
157else:
158 bpf_text = bpf_text.replace('TYPE_FILTER', '!S_ISREG(mode)')
159
Nathan Scottcf0792f2018-02-02 16:56:50 +1100160if debug or args.ebpf:
Brendan Gregg08c29812016-02-09 00:36:43 -0800161 print(bpf_text)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100162 if args.ebpf:
163 exit()
Brendan Gregg08c29812016-02-09 00:36:43 -0800164
165# initialize BPF
166b = BPF(text=bpf_text)
Jazel Canseco6d818b62018-04-13 11:39:56 -0700167b.attach_kprobe(event="vfs_read", fn_name="trace_read_entry")
168b.attach_kprobe(event="vfs_write", fn_name="trace_write_entry")
Mark Drayton74347312016-08-25 20:46:35 +0100169
170DNAME_INLINE_LEN = 32 # linux/dcache.h
Brendan Gregg08c29812016-02-09 00:36:43 -0800171
172print('Tracing... Output every %d secs. Hit Ctrl-C to end' % interval)
173
Joel Fernandes (Google)c2b371d2019-03-08 11:39:42 -0500174def sort_fn(counts):
175 if args.sort == "all":
176 return (counts[1].rbytes + counts[1].wbytes + counts[1].reads + counts[1].writes)
177 else:
178 return getattr(counts[1], args.sort)
179
Brendan Gregg08c29812016-02-09 00:36:43 -0800180# output
181exiting = 0
182while 1:
183 try:
184 sleep(interval)
185 except KeyboardInterrupt:
186 exiting = 1
187
188 # header
189 if clear:
190 call("clear")
191 else:
192 print()
193 with open(loadavg) as stats:
194 print("%-8s loadavg: %s" % (strftime("%H:%M:%S"), stats.read()))
Hengqi Chend5673472021-07-28 23:49:11 +0800195 print("%-7s %-16s %-6s %-6s %-7s %-7s %1s %s" % ("TID", "COMM",
Brendan Gregg08c29812016-02-09 00:36:43 -0800196 "READS", "WRITES", "R_Kb", "W_Kb", "T", "FILE"))
197
Mark Drayton74347312016-08-25 20:46:35 +0100198 # by-TID output
Brendan Gregg08c29812016-02-09 00:36:43 -0800199 counts = b.get_table("counts")
200 line = 0
201 for k, v in reversed(sorted(counts.items(),
Joel Fernandes (Google)c2b371d2019-03-08 11:39:42 -0500202 key=sort_fn)):
jeromemarchandb96ebcd2018-10-10 01:58:15 +0200203 name = k.name.decode('utf-8', 'replace')
Mark Drayton74347312016-08-25 20:46:35 +0100204 if k.name_len > DNAME_INLINE_LEN:
205 name = name[:-3] + "..."
Brendan Gregg08c29812016-02-09 00:36:43 -0800206
207 # print line
Hengqi Chend5673472021-07-28 23:49:11 +0800208 print("%-7d %-16s %-6d %-6d %-7d %-7d %1s %s" % (k.pid,
jeromemarchandb96ebcd2018-10-10 01:58:15 +0200209 k.comm.decode('utf-8', 'replace'), v.reads, v.writes,
210 v.rbytes / 1024, v.wbytes / 1024,
211 k.type.decode('utf-8', 'replace'), name))
Brendan Gregg08c29812016-02-09 00:36:43 -0800212
213 line += 1
214 if line >= maxrows:
215 break
216 counts.clear()
217
218 countdown -= 1
219 if exiting or countdown == 0:
220 print("Detaching...")
221 exit()