blob: 6189da004438c28be68b9f7ae7ecd603917052c9 [file] [log] [blame]
Brendan Greggddce4db2016-02-15 22:24:02 -08001#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
4# btrfsdist Summarize btrfs operation latency.
5# For Linux, uses BCC, eBPF.
6#
7# USAGE: btrfsdist [-h] [-T] [-m] [-p PID] [interval] [count]
8#
9# Copyright 2016 Netflix, Inc.
10# Licensed under the Apache License, Version 2.0 (the "License")
11#
12# 15-Feb-2016 Brendan Gregg Created this.
13
14from __future__ import print_function
15from bcc import BPF
16from time import sleep, strftime
17import argparse
18
19# symbols
20kallsyms = "/proc/kallsyms"
21
22# arguments
23examples = """examples:
24 ./btrfsdist # show operation latency as a histogram
25 ./btrfsdist -p 181 # trace PID 181 only
26 ./btrfsdist 1 10 # print 1 second summaries, 10 times
27 ./btrfsdist -m 5 # 5s summaries, milliseconds
28"""
29parser = argparse.ArgumentParser(
30 description="Summarize btrfs operation latency",
31 formatter_class=argparse.RawDescriptionHelpFormatter,
32 epilog=examples)
33parser.add_argument("-T", "--notimestamp", action="store_true",
34 help="don't include timestamp on interval output")
35parser.add_argument("-m", "--milliseconds", action="store_true",
36 help="output in milliseconds")
37parser.add_argument("-p", "--pid",
38 help="trace this PID only")
39parser.add_argument("interval", nargs="?",
40 help="output interval, in seconds")
41parser.add_argument("count", nargs="?", default=99999999,
42 help="number of outputs")
43args = parser.parse_args()
44pid = args.pid
45countdown = int(args.count)
46if args.milliseconds:
47 factor = 1000000
48 label = "msecs"
49else:
50 factor = 1000
51 label = "usecs"
52if args.interval and int(args.interval) == 0:
53 print("ERROR: interval 0. Exiting.")
54 exit()
55debug = 0
56
57# define BPF program
58bpf_text = """
59#include <uapi/linux/ptrace.h>
60#include <linux/fs.h>
61#include <linux/sched.h>
62
63#define OP_NAME_LEN 8
64typedef struct dist_key {
65 char op[OP_NAME_LEN];
66 u64 slot;
67} dist_key_t;
68BPF_HASH(start, u32);
69BPF_HISTOGRAM(dist, dist_key_t);
70
71// time operation
72int trace_entry(struct pt_regs *ctx)
73{
74 u32 pid = bpf_get_current_pid_tgid();
75 if (FILTER_PID)
76 return 0;
77 u64 ts = bpf_ktime_get_ns();
78 start.update(&pid, &ts);
79 return 0;
80}
81
82// The current btrfs (Linux 4.5) uses generic_file_read_iter() instead of it's
83// own read function. So we need to trace that and then filter on btrfs, which
84// I do by checking file->f_op.
85int trace_read_entry(struct pt_regs *ctx, struct kiocb *iocb)
86{
87 u32 pid = bpf_get_current_pid_tgid();
88 if (FILTER_PID)
89 return 0;
90
91 // btrfs filter on file->f_op == btrfs_file_operations
92 struct file *fp = iocb->ki_filp;
93 if ((u64)fp->f_op != BTRFS_FILE_OPERATIONS)
94 return 0;
95
96 u64 ts = bpf_ktime_get_ns();
97 start.update(&pid, &ts);
98 return 0;
99}
100
101// The current btrfs (Linux 4.5) uses generic_file_open(), instead of it's own
102// function. Same as with reads. Trace the generic path and filter:
103int trace_open_entry(struct pt_regs *ctx, struct inode *inode,
104 struct file *file)
105{
106 u32 pid;
107 pid = bpf_get_current_pid_tgid();
108 if (FILTER_PID)
109 return 0;
110
111 // btrfs filter on file->f_op == btrfs_file_operations
112 if ((u64)file->f_op != BTRFS_FILE_OPERATIONS)
113 return 0;
114
115 u64 ts = bpf_ktime_get_ns();
116 start.update(&pid, &ts);
117 return 0;
118}
119
120static int trace_return(struct pt_regs *ctx, const char *op)
121{
122 u64 *tsp;
123 u32 pid = bpf_get_current_pid_tgid();
124
125 // fetch timestamp and calculate delta
126 tsp = start.lookup(&pid);
127 if (tsp == 0) {
128 return 0; // missed start or filtered
129 }
130 u64 delta = (bpf_ktime_get_ns() - *tsp) / FACTOR;
131
132 // store as histogram
133 dist_key_t key = {.slot = bpf_log2l(delta)};
134 __builtin_memcpy(&key.op, op, sizeof(key.op));
135 dist.increment(key);
136
137 start.delete(&pid);
138 return 0;
139}
140
141int trace_read_return(struct pt_regs *ctx)
142{
143 char *op = "read";
144 return trace_return(ctx, op);
145}
146
147int trace_write_return(struct pt_regs *ctx)
148{
149 char *op = "write";
150 return trace_return(ctx, op);
151}
152
153int trace_open_return(struct pt_regs *ctx)
154{
155 char *op = "open";
156 return trace_return(ctx, op);
157}
158
159int trace_fsync_return(struct pt_regs *ctx)
160{
161 char *op = "fsync";
162 return trace_return(ctx, op);
163}
164"""
165
166# code replacements
167with open(kallsyms) as syms:
168 ops = ''
169 for line in syms:
170 a = line.rstrip().split()
171 (addr, name) = (a[0], a[2])
ygreka5e2ce52016-06-27 12:54:55 -0700172 name = name.split("\t")[0]
Brendan Greggddce4db2016-02-15 22:24:02 -0800173 if name == "btrfs_file_operations":
174 ops = "0x" + addr
175 break
176 if ops == '':
177 print("ERROR: no btrfs_file_operations in /proc/kallsyms. Exiting.")
178 exit()
179 bpf_text = bpf_text.replace('BTRFS_FILE_OPERATIONS', ops)
180bpf_text = bpf_text.replace('FACTOR', str(factor))
181if args.pid:
182 bpf_text = bpf_text.replace('FILTER_PID', 'pid != %s' % pid)
183else:
184 bpf_text = bpf_text.replace('FILTER_PID', '0')
185if debug:
186 print(bpf_text)
187
188# load BPF program
189b = BPF(text=bpf_text)
190
191# Common file functions. See earlier comment about generic_file_read_iter().
192b.attach_kprobe(event="generic_file_read_iter", fn_name="trace_read_entry")
193b.attach_kprobe(event="btrfs_file_write_iter", fn_name="trace_entry")
194b.attach_kprobe(event="generic_file_open", fn_name="trace_open_entry")
195b.attach_kprobe(event="btrfs_sync_file", fn_name="trace_entry")
196b.attach_kretprobe(event="generic_file_read_iter", fn_name="trace_read_return")
197b.attach_kretprobe(event="btrfs_file_write_iter", fn_name="trace_write_return")
198b.attach_kretprobe(event="generic_file_open", fn_name="trace_open_return")
199b.attach_kretprobe(event="btrfs_sync_file", fn_name="trace_fsync_return")
200
201print("Tracing btrfs operation latency... Hit Ctrl-C to end.")
202
203# output
204exiting = 0
205dist = b.get_table("dist")
206while (1):
207 try:
208 if args.interval:
209 sleep(int(args.interval))
210 else:
211 sleep(99999999)
212 except KeyboardInterrupt:
213 exiting = 1
214
215 print()
216 if args.interval and (not args.notimestamp):
217 print(strftime("%H:%M:%S:"))
218
219 dist.print_log2_hist(label, "operation")
220 dist.clear()
221
222 countdown -= 1
223 if exiting or countdown == 0:
224 exit()