• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# @lint-avoid-python-3-compatibility-imports
3#
4# fileslower  Trace slow synchronous file reads and writes.
5#             For Linux, uses BCC, eBPF.
6#
7# USAGE: fileslower [-h] [-p PID] [-a] [min_ms]
8#
9# This script uses kernel dynamic tracing of synchronous reads and writes
10# at the VFS interface, to identify slow file reads and writes for any file
11# system.
12#
13# This works by tracing __vfs_read() and __vfs_write(), and filtering for
14# synchronous I/O (the path to new_sync_read() and new_sync_write()), and
15# for I/O with filenames. This approach provides a view of just two file
16# system request types. There are typically many others: asynchronous I/O,
17# directory operations, file handle operations, etc, that this tool does not
18# instrument.
19#
20# WARNING: This traces VFS reads and writes, which can be extremely frequent,
21# and so the overhead of this tool can become severe depending on the
22# workload.
23#
24# By default, a minimum millisecond threshold of 10 is used.
25#
26# Copyright 2016 Netflix, Inc.
27# Licensed under the Apache License, Version 2.0 (the "License")
28#
29# 06-Feb-2016   Brendan Gregg   Created this.
30
31from __future__ import print_function
32from bcc import BPF
33import argparse
34import ctypes as ct
35import time
36
37# arguments
38examples = """examples:
39    ./fileslower             # trace sync file I/O slower than 10 ms (default)
40    ./fileslower 1           # trace sync file I/O slower than 1 ms
41    ./fileslower -p 185      # trace PID 185 only
42"""
43parser = argparse.ArgumentParser(
44    description="Trace slow synchronous file reads and writes",
45    formatter_class=argparse.RawDescriptionHelpFormatter,
46    epilog=examples)
47parser.add_argument("-p", "--pid", type=int, metavar="PID", dest="tgid",
48    help="trace this PID only")
49parser.add_argument("-a", "--all-files", action="store_true",
50    help="include non-regular file types (sockets, FIFOs, etc)")
51parser.add_argument("min_ms", nargs="?", default='10',
52    help="minimum I/O duration to trace, in ms (default 10)")
53parser.add_argument("--ebpf", action="store_true",
54    help=argparse.SUPPRESS)
55args = parser.parse_args()
56min_ms = int(args.min_ms)
57tgid = args.tgid
58debug = 0
59
60# define BPF program
61bpf_text = """
62#include <uapi/linux/ptrace.h>
63#include <linux/fs.h>
64#include <linux/sched.h>
65
66enum trace_mode {
67    MODE_READ,
68    MODE_WRITE
69};
70
71struct val_t {
72    u32 sz;
73    u64 ts;
74    u32 name_len;
75    // de->d_name.name may point to de->d_iname so limit len accordingly
76    char name[DNAME_INLINE_LEN];
77    char comm[TASK_COMM_LEN];
78};
79
80struct data_t {
81    enum trace_mode mode;
82    u32 pid;
83    u32 sz;
84    u64 delta_us;
85    u32 name_len;
86    char name[DNAME_INLINE_LEN];
87    char comm[TASK_COMM_LEN];
88};
89
90BPF_HASH(entryinfo, pid_t, struct val_t);
91BPF_PERF_OUTPUT(events);
92
93// store timestamp and size on entry
94static int trace_rw_entry(struct pt_regs *ctx, struct file *file,
95    char __user *buf, size_t count)
96{
97    u32 tgid = bpf_get_current_pid_tgid() >> 32;
98    if (TGID_FILTER)
99        return 0;
100
101    u32 pid = bpf_get_current_pid_tgid();
102
103    // skip I/O lacking a filename
104    struct dentry *de = file->f_path.dentry;
105    int mode = file->f_inode->i_mode;
106    if (de->d_name.len == 0 || TYPE_FILTER)
107        return 0;
108
109    // store size and timestamp by pid
110    struct val_t val = {};
111    val.sz = count;
112    val.ts = bpf_ktime_get_ns();
113
114    struct qstr d_name = de->d_name;
115    val.name_len = d_name.len;
116    bpf_probe_read(&val.name, sizeof(val.name), d_name.name);
117    bpf_get_current_comm(&val.comm, sizeof(val.comm));
118    entryinfo.update(&pid, &val);
119
120    return 0;
121}
122
123int trace_read_entry(struct pt_regs *ctx, struct file *file,
124    char __user *buf, size_t count)
125{
126    // skip non-sync I/O; see kernel code for __vfs_read()
127    if (!(file->f_op->read_iter))
128        return 0;
129    return trace_rw_entry(ctx, file, buf, count);
130}
131
132int trace_write_entry(struct pt_regs *ctx, struct file *file,
133    char __user *buf, size_t count)
134{
135    // skip non-sync I/O; see kernel code for __vfs_write()
136    if (!(file->f_op->write_iter))
137        return 0;
138    return trace_rw_entry(ctx, file, buf, count);
139}
140
141// output
142static int trace_rw_return(struct pt_regs *ctx, int type)
143{
144    struct val_t *valp;
145    u32 pid = bpf_get_current_pid_tgid();
146
147    valp = entryinfo.lookup(&pid);
148    if (valp == 0) {
149        // missed tracing issue or filtered
150        return 0;
151    }
152    u64 delta_us = (bpf_ktime_get_ns() - valp->ts) / 1000;
153    entryinfo.delete(&pid);
154    if (delta_us < MIN_US)
155        return 0;
156
157    struct data_t data = {};
158    data.mode = type;
159    data.pid = pid;
160    data.sz = valp->sz;
161    data.delta_us = delta_us;
162    data.name_len = valp->name_len;
163    bpf_probe_read(&data.name, sizeof(data.name), valp->name);
164    bpf_probe_read(&data.comm, sizeof(data.comm), valp->comm);
165    events.perf_submit(ctx, &data, sizeof(data));
166
167    return 0;
168}
169
170int trace_read_return(struct pt_regs *ctx)
171{
172    return trace_rw_return(ctx, MODE_READ);
173}
174
175int trace_write_return(struct pt_regs *ctx)
176{
177    return trace_rw_return(ctx, MODE_WRITE);
178}
179
180"""
181bpf_text = bpf_text.replace('MIN_US', str(min_ms * 1000))
182if args.tgid:
183    bpf_text = bpf_text.replace('TGID_FILTER', 'tgid != %d' % tgid)
184else:
185    bpf_text = bpf_text.replace('TGID_FILTER', '0')
186if args.all_files:
187    bpf_text = bpf_text.replace('TYPE_FILTER', '0')
188else:
189    bpf_text = bpf_text.replace('TYPE_FILTER', '!S_ISREG(mode)')
190
191if debug or args.ebpf:
192    print(bpf_text)
193    if args.ebpf:
194        exit()
195
196# initialize BPF
197b = BPF(text=bpf_text)
198
199# I'd rather trace these via new_sync_read/new_sync_write (which used to be
200# do_sync_read/do_sync_write), but those became static. So trace these from
201# the parent functions, at the cost of more overhead, instead.
202# Ultimately, we should be using [V]FS tracepoints.
203b.attach_kprobe(event="__vfs_read", fn_name="trace_read_entry")
204b.attach_kretprobe(event="__vfs_read", fn_name="trace_read_return")
205try:
206    b.attach_kprobe(event="__vfs_write", fn_name="trace_write_entry")
207    b.attach_kretprobe(event="__vfs_write", fn_name="trace_write_return")
208except Exception:
209    # older kernels don't have __vfs_write so try vfs_write instead
210    b.attach_kprobe(event="vfs_write", fn_name="trace_write_entry")
211    b.attach_kretprobe(event="vfs_write", fn_name="trace_write_return")
212
213TASK_COMM_LEN = 16  # linux/sched.h
214DNAME_INLINE_LEN = 32  # linux/dcache.h
215
216class Data(ct.Structure):
217    _fields_ = [
218        ("mode", ct.c_int),
219        ("pid", ct.c_uint),
220        ("sz", ct.c_uint),
221        ("delta_us", ct.c_ulonglong),
222        ("name_len", ct.c_uint),
223        ("name", ct.c_char * DNAME_INLINE_LEN),
224        ("comm", ct.c_char * TASK_COMM_LEN),
225    ]
226
227mode_s = {
228    0: 'R',
229    1: 'W',
230}
231
232# header
233print("Tracing sync read/writes slower than %d ms" % min_ms)
234print("%-8s %-14s %-6s %1s %-7s %7s %s" % ("TIME(s)", "COMM", "TID", "D",
235    "BYTES", "LAT(ms)", "FILENAME"))
236
237start_ts = time.time()
238
239def print_event(cpu, data, size):
240    event = ct.cast(data, ct.POINTER(Data)).contents
241
242    ms = float(event.delta_us) / 1000
243    name = event.name.decode('utf-8', 'replace')
244    if event.name_len > DNAME_INLINE_LEN:
245        name = name[:-3] + "..."
246
247    print("%-8.3f %-14.14s %-6s %1s %-7s %7.2f %s" % (
248        time.time() - start_ts, event.comm.decode('utf-8', 'replace'),
249        event.pid, mode_s[event.mode], event.sz, ms, name))
250
251b["events"].open_perf_buffer(print_event, page_cnt=64)
252while 1:
253    try:
254        b.perf_buffer_poll()
255    except KeyboardInterrupt:
256        exit()
257