blob: 2ade0695244f7f36f3a98450940ee0a2711744e9 [file] [log] [blame]
Alexey Ivanovcc01a9c2019-01-16 09:50:46 -08001#!/usr/bin/python
Alexei Starovoitovbdf07732016-01-14 10:09:20 -08002# @lint-avoid-python-3-compatibility-imports
Brendan Gregg74016c32015-09-21 15:49:21 -07003#
Sasha Goldshteina466c462016-10-06 21:17:59 +03004# funclatency Time functions and print latency as a histogram.
Alexei Starovoitovbdf07732016-01-14 10:09:20 -08005# For Linux, uses BCC, eBPF.
Brendan Gregg74016c32015-09-21 15:49:21 -07006#
Sasha Goldshteina466c462016-10-06 21:17:59 +03007# USAGE: funclatency [-h] [-p PID] [-i INTERVAL] [-T] [-u] [-m] [-F] [-r] [-v]
8# pattern
Brendan Gregg74016c32015-09-21 15:49:21 -07009#
10# Run "funclatency -h" for full usage.
11#
Sasha Goldshteinf41ae862016-10-19 01:14:30 +030012# The pattern is a string with optional '*' wildcards, similar to file
13# globbing. If you'd prefer to use regular expressions, use the -r option.
Brendan Gregg74016c32015-09-21 15:49:21 -070014#
chenyue.zhou0cf81662021-04-14 13:37:07 -040015# Without the '-l' option, only the innermost calls will be recorded.
16# Use '-l LEVEL' to record the outermost n levels of nested/recursive functions.
17#
Brendan Gregg74016c32015-09-21 15:49:21 -070018# Copyright (c) 2015 Brendan Gregg.
chenyue.zhou0cf81662021-04-14 13:37:07 -040019# Copyright (c) 2021 Chenyue Zhou.
Brendan Gregg74016c32015-09-21 15:49:21 -070020# Licensed under the Apache License, Version 2.0 (the "License")
21#
Sasha Goldshteina466c462016-10-06 21:17:59 +030022# 20-Sep-2015 Brendan Gregg Created this.
23# 06-Oct-2016 Sasha Goldshtein Added user function support.
chenyue.zhou0cf81662021-04-14 13:37:07 -040024# 14-Apr-2021 Chenyue Zhou Added nested or recursive function support.
Brendan Gregg74016c32015-09-21 15:49:21 -070025
26from __future__ import print_function
27from bcc import BPF
28from time import sleep, strftime
29import argparse
30import signal
31
32# arguments
33examples = """examples:
Sasha Goldshteina466c462016-10-06 21:17:59 +030034 ./funclatency do_sys_open # time the do_sys_open() kernel function
35 ./funclatency c:read # time the read() C library function
Brendan Gregg74016c32015-09-21 15:49:21 -070036 ./funclatency -u vfs_read # time vfs_read(), in microseconds
37 ./funclatency -m do_nanosleep # time do_nanosleep(), in milliseconds
Akilesh Kailash89967192018-05-18 13:36:54 -070038 ./funclatency -i 2 -d 10 c:open # output every 2 seconds, for duration 10s
Brendan Gregg74016c32015-09-21 15:49:21 -070039 ./funclatency -mTi 5 vfs_read # output every 5 seconds, with timestamps
40 ./funclatency -p 181 vfs_read # time process 181 only
41 ./funclatency 'vfs_fstat*' # time both vfs_fstat() and vfs_fstatat()
Sasha Goldshteina466c462016-10-06 21:17:59 +030042 ./funclatency 'c:*printf' # time the *printf family of functions
Brendan Gregg50bbca42015-09-25 12:47:53 -070043 ./funclatency -F 'vfs_r*' # show one histogram per matched function
Brendan Gregg74016c32015-09-21 15:49:21 -070044"""
45parser = argparse.ArgumentParser(
Sasha Goldshteina466c462016-10-06 21:17:59 +030046 description="Time functions and print latency as a histogram",
Alexei Starovoitovbdf07732016-01-14 10:09:20 -080047 formatter_class=argparse.RawDescriptionHelpFormatter,
48 epilog=examples)
htbegin5ac5d6e2017-05-24 22:53:17 +080049parser.add_argument("-p", "--pid", type=int,
Alexei Starovoitovbdf07732016-01-14 10:09:20 -080050 help="trace this PID only")
Akilesh Kailash89967192018-05-18 13:36:54 -070051parser.add_argument("-i", "--interval", type=int,
52 help="summary interval, in seconds")
53parser.add_argument("-d", "--duration", type=int,
54 help="total duration of trace, in seconds")
Brendan Gregg74016c32015-09-21 15:49:21 -070055parser.add_argument("-T", "--timestamp", action="store_true",
Alexei Starovoitovbdf07732016-01-14 10:09:20 -080056 help="include timestamp on output")
Brendan Gregg74016c32015-09-21 15:49:21 -070057parser.add_argument("-u", "--microseconds", action="store_true",
Alexei Starovoitovbdf07732016-01-14 10:09:20 -080058 help="microsecond histogram")
Brendan Gregg74016c32015-09-21 15:49:21 -070059parser.add_argument("-m", "--milliseconds", action="store_true",
Alexei Starovoitovbdf07732016-01-14 10:09:20 -080060 help="millisecond histogram")
Brendan Gregg50bbca42015-09-25 12:47:53 -070061parser.add_argument("-F", "--function", action="store_true",
Alexei Starovoitovbdf07732016-01-14 10:09:20 -080062 help="show a separate histogram per function")
Brendan Gregg74016c32015-09-21 15:49:21 -070063parser.add_argument("-r", "--regexp", action="store_true",
Alexei Starovoitovbdf07732016-01-14 10:09:20 -080064 help="use regular expressions. Default is \"*\" wildcards only.")
chenyue.zhouc76cd922021-04-06 16:55:48 -040065parser.add_argument("-l", "--level", type=int,
66 help="set the level of nested or recursive functions")
Sasha Goldshteina466c462016-10-06 21:17:59 +030067parser.add_argument("-v", "--verbose", action="store_true",
68 help="print the BPF program (for debugging purposes)")
Brendan Gregg74016c32015-09-21 15:49:21 -070069parser.add_argument("pattern",
Sasha Goldshteina466c462016-10-06 21:17:59 +030070 help="search expression for functions")
Nathan Scottcf0792f2018-02-02 16:56:50 +110071parser.add_argument("--ebpf", action="store_true",
72 help=argparse.SUPPRESS)
Brendan Gregg74016c32015-09-21 15:49:21 -070073args = parser.parse_args()
Akilesh Kailash89967192018-05-18 13:36:54 -070074if args.duration and not args.interval:
75 args.interval = args.duration
76if not args.interval:
77 args.interval = 99999999
Sasha Goldshteina466c462016-10-06 21:17:59 +030078
79def bail(error):
80 print("Error: " + error)
81 exit(1)
82
83parts = args.pattern.split(':')
84if len(parts) == 1:
85 library = None
86 pattern = args.pattern
87elif len(parts) == 2:
88 library = parts[0]
89 libpath = BPF.find_library(library) or BPF.find_exe(library)
90 if not libpath:
91 bail("can't resolve library %s" % library)
92 library = libpath
93 pattern = parts[1]
94else:
95 bail("unrecognized pattern format '%s'" % pattern)
96
Brendan Gregg74016c32015-09-21 15:49:21 -070097if not args.regexp:
Alexei Starovoitovbdf07732016-01-14 10:09:20 -080098 pattern = pattern.replace('*', '.*')
99 pattern = '^' + pattern + '$'
Brendan Gregg74016c32015-09-21 15:49:21 -0700100
101# define BPF program
102bpf_text = """
103#include <uapi/linux/ptrace.h>
Brendan Gregg74016c32015-09-21 15:49:21 -0700104
Sasha Goldshteina466c462016-10-06 21:17:59 +0300105typedef struct ip_pid {
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800106 u64 ip;
Sasha Goldshteina466c462016-10-06 21:17:59 +0300107 u64 pid;
108} ip_pid_t;
109
110typedef struct hist_key {
111 ip_pid_t key;
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800112 u64 slot;
Sasha Goldshteina466c462016-10-06 21:17:59 +0300113} hist_key_t;
Brendan Gregg50bbca42015-09-25 12:47:53 -0700114
chenyue.zhouc76cd922021-04-06 16:55:48 -0400115TYPEDEF
116
Haoaf90bd42020-09-29 02:01:36 +0800117BPF_ARRAY(avg, u64, 2);
Brendan Gregg50bbca42015-09-25 12:47:53 -0700118STORAGE
chenyue.zhouc76cd922021-04-06 16:55:48 -0400119FUNCTION
Brendan Gregg74016c32015-09-21 15:49:21 -0700120
121int trace_func_entry(struct pt_regs *ctx)
122{
Sasha Goldshteina466c462016-10-06 21:17:59 +0300123 u64 pid_tgid = bpf_get_current_pid_tgid();
124 u32 pid = pid_tgid;
125 u32 tgid = pid_tgid >> 32;
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800126 u64 ts = bpf_ktime_get_ns();
Brendan Gregg74016c32015-09-21 15:49:21 -0700127
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800128 FILTER
129 ENTRYSTORE
Brendan Gregg74016c32015-09-21 15:49:21 -0700130
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800131 return 0;
Brendan Gregg74016c32015-09-21 15:49:21 -0700132}
133
134int trace_func_return(struct pt_regs *ctx)
135{
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800136 u64 *tsp, delta;
Sasha Goldshteina466c462016-10-06 21:17:59 +0300137 u64 pid_tgid = bpf_get_current_pid_tgid();
138 u32 pid = pid_tgid;
139 u32 tgid = pid_tgid >> 32;
Brendan Gregg74016c32015-09-21 15:49:21 -0700140
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800141 // calculate delta time
chenyue.zhouc76cd922021-04-06 16:55:48 -0400142 CALCULATE
Haoaf90bd42020-09-29 02:01:36 +0800143
144 u32 lat = 0;
145 u32 cnt = 1;
chenyuezhou08a22032021-06-21 10:07:30 -0400146 avg.atomic_increment(lat, delta);
147 avg.atomic_increment(cnt);
Haoaf90bd42020-09-29 02:01:36 +0800148
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800149 FACTOR
Brendan Gregg74016c32015-09-21 15:49:21 -0700150
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800151 // store as histogram
152 STORE
Brendan Gregg74016c32015-09-21 15:49:21 -0700153
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800154 return 0;
Brendan Gregg74016c32015-09-21 15:49:21 -0700155}
156"""
Brendan Gregg50bbca42015-09-25 12:47:53 -0700157
Sasha Goldshteina466c462016-10-06 21:17:59 +0300158# do we need to store the IP and pid for each invocation?
159need_key = args.function or (library and not args.pid)
160
Brendan Gregg50bbca42015-09-25 12:47:53 -0700161# code substitutions
Brendan Gregg74016c32015-09-21 15:49:21 -0700162if args.pid:
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800163 bpf_text = bpf_text.replace('FILTER',
htbegin5ac5d6e2017-05-24 22:53:17 +0800164 'if (tgid != %d) { return 0; }' % args.pid)
Brendan Gregg74016c32015-09-21 15:49:21 -0700165else:
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800166 bpf_text = bpf_text.replace('FILTER', '')
Brendan Gregg74016c32015-09-21 15:49:21 -0700167if args.milliseconds:
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800168 bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000000;')
169 label = "msecs"
Brendan Gregg74016c32015-09-21 15:49:21 -0700170elif args.microseconds:
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800171 bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000;')
172 label = "usecs"
Brendan Gregg74016c32015-09-21 15:49:21 -0700173else:
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800174 bpf_text = bpf_text.replace('FACTOR', '')
175 label = "nsecs"
Sasha Goldshteina466c462016-10-06 21:17:59 +0300176if need_key:
Sasha Goldshteina466c462016-10-06 21:17:59 +0300177 pid = '-1' if not library else 'tgid'
chenyue.zhouc76cd922021-04-06 16:55:48 -0400178
chenyue.zhou0cf81662021-04-14 13:37:07 -0400179 if args.level and args.level > 1:
chenyue.zhouc76cd922021-04-06 16:55:48 -0400180 bpf_text = bpf_text.replace('TYPEDEF',
181 """
182#define STACK_DEPTH %s
183
184typedef struct {
185 u64 ip;
186 u64 start_ts;
187} func_cache_t;
188
189/* LIFO */
190typedef struct {
191 u32 head;
192 func_cache_t cache[STACK_DEPTH];
193} func_stack_t;
194 """ % args.level)
195
196 bpf_text = bpf_text.replace('STORAGE',
197 """
198BPF_HASH(func_stack, u32, func_stack_t);
199BPF_HISTOGRAM(dist, hist_key_t);
200 """)
201
202 bpf_text = bpf_text.replace('FUNCTION',
203 """
204static inline int stack_pop(func_stack_t *stack, func_cache_t *cache) {
205 if (stack->head <= 0) {
206 return -1;
207 }
208
209 u32 index = --stack->head;
chenyue.zhouc76cd922021-04-06 16:55:48 -0400210 if (index < STACK_DEPTH) {
211 /* bound check */
212 cache->ip = stack->cache[index].ip;
213 cache->start_ts = stack->cache[index].start_ts;
214 }
215
216 return 0;
217}
218
219static inline int stack_push(func_stack_t *stack, func_cache_t *cache) {
220 u32 index = stack->head;
221
222 if (index > STACK_DEPTH - 1) {
223 /* bound check */
chenyue.zhouc76cd922021-04-06 16:55:48 -0400224 return -1;
225 }
226
227 stack->head++;
chenyue.zhouc76cd922021-04-06 16:55:48 -0400228 stack->cache[index].ip = cache->ip;
229 stack->cache[index].start_ts = cache->start_ts;
230
231 return 0;
232}
233 """)
234
235 bpf_text = bpf_text.replace('ENTRYSTORE',
236 """
237 u64 ip = PT_REGS_IP(ctx);
238 func_cache_t cache = {
239 .ip = ip,
240 .start_ts = ts,
241 };
242
243 func_stack_t *stack = func_stack.lookup(&pid);
244 if (!stack) {
245 func_stack_t new_stack = {
246 .head = 0,
247 };
248
249 if (!stack_push(&new_stack, &cache)) {
250 func_stack.update(&pid, &new_stack);
251 }
252
253 return 0;
254 }
255
256 if (!stack_push(stack, &cache)) {
257 func_stack.update(&pid, stack);
258 }
259 """)
260
261 bpf_text = bpf_text.replace('CALCULATE',
262 """
263 u64 ip, start_ts;
264 func_stack_t *stack = func_stack.lookup(&pid);
265 if (!stack) {
266 /* miss start */
chenyue.zhouc76cd922021-04-06 16:55:48 -0400267 return 0;
268 }
269
270 func_cache_t cache = {};
271 if (stack_pop(stack, &cache)) {
272 func_stack.delete(&pid);
273
274 return 0;
275 }
chenyue.zhouc76cd922021-04-06 16:55:48 -0400276 ip = cache.ip;
277 start_ts = cache.start_ts;
278 delta = bpf_ktime_get_ns() - start_ts;
279 """)
280
281 bpf_text = bpf_text.replace('STORE',
282 """
283 hist_key_t key;
284 key.key.ip = ip;
285 key.key.pid = %s;
286 key.slot = bpf_log2l(delta);
zcy80242fb2021-07-02 00:12:32 +0800287 dist.atomic_increment(key);
chenyue.zhouc76cd922021-04-06 16:55:48 -0400288
289 if (stack->head == 0) {
290 /* empty */
chenyue.zhouc76cd922021-04-06 16:55:48 -0400291 func_stack.delete(&pid);
292 }
293 """ % pid)
294
295 else:
296 bpf_text = bpf_text.replace('STORAGE', 'BPF_HASH(ipaddr, u32);\n'\
297 'BPF_HISTOGRAM(dist, hist_key_t);\n'\
298 'BPF_HASH(start, u32);')
299 # stash the IP on entry, as on return it's kretprobe_trampoline:
300 bpf_text = bpf_text.replace('ENTRYSTORE',
301 'u64 ip = PT_REGS_IP(ctx); ipaddr.update(&pid, &ip);'\
302 ' start.update(&pid, &ts);')
303 bpf_text = bpf_text.replace('STORE',
304 """
Sasha Goldshteina466c462016-10-06 21:17:59 +0300305 u64 ip, *ipp = ipaddr.lookup(&pid);
306 if (ipp) {
307 ip = *ipp;
308 hist_key_t key;
309 key.key.ip = ip;
310 key.key.pid = %s;
311 key.slot = bpf_log2l(delta);
zcy80242fb2021-07-02 00:12:32 +0800312 dist.atomic_increment(key);
Sasha Goldshteina466c462016-10-06 21:17:59 +0300313 ipaddr.delete(&pid);
314 }
chenyue.zhouc76cd922021-04-06 16:55:48 -0400315 """ % pid)
Brendan Gregg50bbca42015-09-25 12:47:53 -0700316else:
chenyue.zhouc76cd922021-04-06 16:55:48 -0400317 bpf_text = bpf_text.replace('STORAGE', 'BPF_HISTOGRAM(dist);\n'\
318 'BPF_HASH(start, u32);')
319 bpf_text = bpf_text.replace('ENTRYSTORE', 'start.update(&pid, &ts);')
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800320 bpf_text = bpf_text.replace('STORE',
zcy80242fb2021-07-02 00:12:32 +0800321 'dist.atomic_increment(bpf_log2l(delta));')
chenyue.zhouc76cd922021-04-06 16:55:48 -0400322
323bpf_text = bpf_text.replace('TYPEDEF', '')
324bpf_text = bpf_text.replace('FUNCTION', '')
325bpf_text = bpf_text.replace('CALCULATE',
326 """
327 tsp = start.lookup(&pid);
328 if (tsp == 0) {
329 return 0; // missed start
330 }
331 delta = bpf_ktime_get_ns() - *tsp;
332 start.delete(&pid);
333 """)
334
Nathan Scottcf0792f2018-02-02 16:56:50 +1100335if args.verbose or args.ebpf:
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800336 print(bpf_text)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100337 if args.ebpf:
338 exit()
Brendan Gregg74016c32015-09-21 15:49:21 -0700339
340# signal handler
341def signal_ignore(signal, frame):
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800342 print()
Brendan Gregg74016c32015-09-21 15:49:21 -0700343
344# load BPF program
345b = BPF(text=bpf_text)
Sasha Goldshteina466c462016-10-06 21:17:59 +0300346
347# attach probes
348if not library:
349 b.attach_kprobe(event_re=pattern, fn_name="trace_func_entry")
350 b.attach_kretprobe(event_re=pattern, fn_name="trace_func_return")
351 matched = b.num_open_kprobes()
352else:
Paul Chaignond73c58f2017-01-21 14:25:41 +0100353 b.attach_uprobe(name=library, sym_re=pattern, fn_name="trace_func_entry",
354 pid=args.pid or -1)
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300355 b.attach_uretprobe(name=library, sym_re=pattern,
Paul Chaignond73c58f2017-01-21 14:25:41 +0100356 fn_name="trace_func_return", pid=args.pid or -1)
Sasha Goldshteina466c462016-10-06 21:17:59 +0300357 matched = b.num_open_uprobes()
358
Brendan Gregg6b8add02015-09-25 11:16:33 -0700359if matched == 0:
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800360 print("0 functions matched by \"%s\". Exiting." % args.pattern)
361 exit()
Brendan Gregg74016c32015-09-21 15:49:21 -0700362
363# header
Brendan Gregg6b8add02015-09-25 11:16:33 -0700364print("Tracing %d functions for \"%s\"... Hit Ctrl-C to end." %
365 (matched / 2, args.pattern))
Brendan Gregg74016c32015-09-21 15:49:21 -0700366
367# output
Sasha Goldshteina466c462016-10-06 21:17:59 +0300368def print_section(key):
369 if not library:
370 return BPF.sym(key[0], -1)
371 else:
372 return "%s [%d]" % (BPF.sym(key[0], key[1]), key[1])
373
Brendan Gregg74016c32015-09-21 15:49:21 -0700374exiting = 0 if args.interval else 1
Akilesh Kailash89967192018-05-18 13:36:54 -0700375seconds = 0
Brendan Gregg74016c32015-09-21 15:49:21 -0700376dist = b.get_table("dist")
377while (1):
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800378 try:
Akilesh Kailash89967192018-05-18 13:36:54 -0700379 sleep(args.interval)
380 seconds += args.interval
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800381 except KeyboardInterrupt:
382 exiting = 1
383 # as cleanup can take many seconds, trap Ctrl-C:
384 signal.signal(signal.SIGINT, signal_ignore)
Akilesh Kailash89967192018-05-18 13:36:54 -0700385 if args.duration and seconds >= args.duration:
386 exiting = 1
Brendan Gregg74016c32015-09-21 15:49:21 -0700387
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800388 print()
389 if args.timestamp:
390 print("%-8s\n" % strftime("%H:%M:%S"), end="")
Brendan Gregg74016c32015-09-21 15:49:21 -0700391
Sasha Goldshteina466c462016-10-06 21:17:59 +0300392 if need_key:
393 dist.print_log2_hist(label, "Function", section_print_fn=print_section,
394 bucket_fn=lambda k: (k.ip, k.pid))
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800395 else:
396 dist.print_log2_hist(label)
Brendan Gregg74016c32015-09-21 15:49:21 -0700397
Haoaf90bd42020-09-29 02:01:36 +0800398 total = b['avg'][0].value
399 counts = b['avg'][1].value
400 if counts > 0:
401 if label == 'msecs':
402 total /= 1000000
403 elif label == 'usecs':
404 total /= 1000
405 avg = total/counts
406 print("\navg = %ld %s, total: %ld %s, count: %ld\n" %(total/counts, label, total, label, counts))
407
netedwardwue0c8c102021-04-28 12:53:26 +0800408 dist.clear()
409 b['avg'].clear()
410
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800411 if exiting:
412 print("Detaching...")
413 exit()