blob: 7cdf390f7e421c0c945e4c1e444f342a2e9c0637 [file] [log] [blame]
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -07001#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
Sasha Goldshteina245c792016-10-25 02:18:35 -07004# ucalls Summarize method calls in high-level languages and/or system calls.
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -07005# For Linux, uses BCC, eBPF.
6#
Sasha Goldshteincfb5ee72017-02-08 14:32:51 -05007# USAGE: ucalls [-l {java,python,ruby,php}] [-h] [-T TOP] [-L] [-S] [-v] [-m]
Sasha Goldshteina245c792016-10-25 02:18:35 -07008# pid [interval]
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -07009#
10# Copyright 2016 Sasha Goldshtein
11# Licensed under the Apache License, Version 2.0 (the "License")
12#
13# 19-Oct-2016 Sasha Goldshtein Created this.
14
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070015from __future__ import print_function
16import argparse
17from bcc import BPF, USDT
18from time import sleep
19
20examples = """examples:
Sasha Goldshteina245c792016-10-25 02:18:35 -070021 ./ucalls -l java 185 # trace Java calls and print statistics on ^C
22 ./ucalls -l python 2020 1 # trace Python calls and print every second
23 ./ucalls -l java 185 -S # trace Java calls and syscalls
24 ./ucalls 6712 -S # trace only syscall counts
25 ./ucalls -l ruby 1344 -T 10 # trace top 10 Ruby method calls
26 ./ucalls -l ruby 1344 -L # trace Ruby calls including latency
Sasha Goldshteincfb5ee72017-02-08 14:32:51 -050027 ./ucalls -l php 443 -LS # trace PHP calls and syscalls with latency
Sasha Goldshteina245c792016-10-25 02:18:35 -070028 ./ucalls -l python 2020 -mL # trace Python calls including latency in ms
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070029"""
30parser = argparse.ArgumentParser(
31 description="Summarize method calls in high-level languages.",
32 formatter_class=argparse.RawDescriptionHelpFormatter,
33 epilog=examples)
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070034parser.add_argument("pid", type=int, help="process id to attach to")
35parser.add_argument("interval", type=int, nargs='?',
36 help="print every specified number of seconds")
Sasha Goldshteincfb5ee72017-02-08 14:32:51 -050037parser.add_argument("-l", "--language",
38 choices=["java", "python", "ruby", "php"],
Sasha Goldshteina245c792016-10-25 02:18:35 -070039 help="language to trace (if none, trace syscalls only)")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070040parser.add_argument("-T", "--top", type=int,
41 help="number of most frequent/slow calls to print")
42parser.add_argument("-L", "--latency", action="store_true",
43 help="record method latency from enter to exit (except recursive calls)")
Sasha Goldshteina245c792016-10-25 02:18:35 -070044parser.add_argument("-S", "--syscalls", action="store_true",
45 help="record syscall latency (adds overhead)")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070046parser.add_argument("-v", "--verbose", action="store_true",
47 help="verbose mode: print the BPF program (for debugging purposes)")
48parser.add_argument("-m", "--milliseconds", action="store_true",
49 help="report times in milliseconds (default is microseconds)")
50args = parser.parse_args()
51
52# We assume that the entry and return probes have the same arguments. This is
Sasha Goldshteincfb5ee72017-02-08 14:32:51 -050053# the case for Java, Python, Ruby, and PHP. If there's a language where it's
54# not the case, we will need to build a custom correlator from entry to exit.
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070055if args.language == "java":
56 # TODO for JVM entries, we actually have the real length of the class
Sasha Goldshteina245c792016-10-25 02:18:35 -070057 # and method strings in arg3 and arg5 respectively, so we can insert
58 # the null terminator in its proper position.
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070059 entry_probe = "method__entry"
60 return_probe = "method__return"
61 read_class = "bpf_usdt_readarg(2, ctx, &clazz);"
62 read_method = "bpf_usdt_readarg(4, ctx, &method);"
63elif args.language == "python":
64 entry_probe = "function__entry"
65 return_probe = "function__return"
66 read_class = "bpf_usdt_readarg(1, ctx, &clazz);" # filename really
67 read_method = "bpf_usdt_readarg(2, ctx, &method);"
68elif args.language == "ruby":
Sasha Goldshtein6e5c6212016-10-25 04:30:54 -070069 # TODO Also probe cmethod__entry and cmethod__return with same arguments
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070070 entry_probe = "method__entry"
71 return_probe = "method__return"
72 read_class = "bpf_usdt_readarg(1, ctx, &clazz);"
73 read_method = "bpf_usdt_readarg(2, ctx, &method);"
Sasha Goldshteincfb5ee72017-02-08 14:32:51 -050074elif args.language == "php":
75 entry_probe = "function__entry"
76 return_probe = "function__return"
77 read_class = "bpf_usdt_readarg(4, ctx, &clazz);"
78 read_method = "bpf_usdt_readarg(1, ctx, &method);"
Sasha Goldshteina245c792016-10-25 02:18:35 -070079elif not args.language:
80 if not args.syscalls:
81 print("Nothing to do; use -S to trace syscalls.")
82 exit(1)
83 entry_probe, return_probe, read_class, read_method = ("", "", "", "")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070084
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070085program = """
Sasha Goldshteina245c792016-10-25 02:18:35 -070086#include <linux/ptrace.h>
87
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070088#define MAX_STRING_LENGTH 80
Sasha Goldshteina245c792016-10-25 02:18:35 -070089DEFINE_NOLANG
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070090DEFINE_LATENCY
Sasha Goldshteina245c792016-10-25 02:18:35 -070091DEFINE_SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070092
93struct method_t {
94 char clazz[MAX_STRING_LENGTH];
95 char method[MAX_STRING_LENGTH];
96};
97struct entry_t {
98 u64 pid;
99 struct method_t method;
100};
101struct info_t {
102 u64 num_calls;
103 u64 total_ns;
104};
Sasha Goldshteina245c792016-10-25 02:18:35 -0700105struct syscall_entry_t {
106 u64 timestamp;
107 u64 ip;
108};
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700109
110#ifndef LATENCY
Sasha Goldshteina245c792016-10-25 02:18:35 -0700111 BPF_HASH(counts, struct method_t, u64); // number of calls
112 #ifdef SYSCALLS
113 BPF_HASH(syscounts, u64, u64); // number of calls per IP
114 #endif // SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700115#else
Sasha Goldshteina245c792016-10-25 02:18:35 -0700116 BPF_HASH(times, struct method_t, struct info_t);
117 BPF_HASH(entry, struct entry_t, u64); // timestamp at entry
118 #ifdef SYSCALLS
119 BPF_HASH(systimes, u64, struct info_t); // latency per IP
120 BPF_HASH(sysentry, u64, struct syscall_entry_t); // ts + IP at entry
121 #endif // SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700122#endif
123
Sasha Goldshteina245c792016-10-25 02:18:35 -0700124#ifndef NOLANG
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700125int trace_entry(struct pt_regs *ctx) {
126 u64 clazz = 0, method = 0, val = 0;
127 u64 *valp;
128 struct entry_t data = {0};
129#ifdef LATENCY
130 u64 timestamp = bpf_ktime_get_ns();
131 data.pid = bpf_get_current_pid_tgid();
132#endif
133 READ_CLASS
134 READ_METHOD
135 bpf_probe_read(&data.method.clazz, sizeof(data.method.clazz),
136 (void *)clazz);
137 bpf_probe_read(&data.method.method, sizeof(data.method.method),
138 (void *)method);
139#ifndef LATENCY
140 valp = counts.lookup_or_init(&data.method, &val);
141 ++(*valp);
142#endif
143#ifdef LATENCY
144 entry.update(&data, &timestamp);
145#endif
146 return 0;
147}
148
149#ifdef LATENCY
150int trace_return(struct pt_regs *ctx) {
151 u64 *entry_timestamp, clazz = 0, method = 0;
152 struct info_t *info, zero = {};
153 struct entry_t data = {};
154 data.pid = bpf_get_current_pid_tgid();
155 READ_CLASS
156 READ_METHOD
157 bpf_probe_read(&data.method.clazz, sizeof(data.method.clazz),
158 (void *)clazz);
159 bpf_probe_read(&data.method.method, sizeof(data.method.method),
160 (void *)method);
161 entry_timestamp = entry.lookup(&data);
162 if (!entry_timestamp) {
163 return 0; // missed the entry event
164 }
165 info = times.lookup_or_init(&data.method, &zero);
166 info->num_calls += 1;
167 info->total_ns += bpf_ktime_get_ns() - *entry_timestamp;
168 entry.delete(&data);
169 return 0;
170}
Sasha Goldshteina245c792016-10-25 02:18:35 -0700171#endif // LATENCY
172#endif // NOLANG
173
174#ifdef SYSCALLS
175int syscall_entry(struct pt_regs *ctx) {
176 u64 pid = bpf_get_current_pid_tgid();
177 u64 *valp, ip = ctx->ip, val = 0;
178 PID_FILTER
179#ifdef LATENCY
180 struct syscall_entry_t data = {};
181 data.timestamp = bpf_ktime_get_ns();
182 data.ip = ip;
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700183#endif
Sasha Goldshteina245c792016-10-25 02:18:35 -0700184#ifndef LATENCY
185 valp = syscounts.lookup_or_init(&ip, &val);
186 ++(*valp);
187#endif
188#ifdef LATENCY
189 sysentry.update(&pid, &data);
190#endif
191 return 0;
192}
193
194#ifdef LATENCY
195int syscall_return(struct pt_regs *ctx) {
196 struct syscall_entry_t *e;
197 struct info_t *info, zero = {};
198 u64 pid = bpf_get_current_pid_tgid(), ip;
199 PID_FILTER
200 e = sysentry.lookup(&pid);
201 if (!e) {
202 return 0; // missed the entry event
203 }
204 ip = e->ip;
205 info = systimes.lookup_or_init(&ip, &zero);
206 info->num_calls += 1;
207 info->total_ns += bpf_ktime_get_ns() - e->timestamp;
208 sysentry.delete(&pid);
209 return 0;
210}
211#endif // LATENCY
212#endif // SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700213""".replace("READ_CLASS", read_class) \
214 .replace("READ_METHOD", read_method) \
Sasha Goldshteina245c792016-10-25 02:18:35 -0700215 .replace("PID_FILTER", "if ((pid >> 32) != %d) { return 0; }" % args.pid) \
216 .replace("DEFINE_NOLANG", "#define NOLANG" if not args.language else "") \
217 .replace("DEFINE_LATENCY", "#define LATENCY" if args.latency else "") \
218 .replace("DEFINE_SYSCALLS", "#define SYSCALLS" if args.syscalls else "")
219
220if args.language:
221 usdt = USDT(pid=args.pid)
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -0500222 usdt.enable_probe_or_bail(entry_probe, "trace_entry")
Sasha Goldshteina245c792016-10-25 02:18:35 -0700223 if args.latency:
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -0500224 usdt.enable_probe_or_bail(return_probe, "trace_return")
Sasha Goldshteina245c792016-10-25 02:18:35 -0700225else:
226 usdt = None
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700227
228if args.verbose:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700229 if usdt:
230 print(usdt.get_text())
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700231 print(program)
232
Sasha Goldshteina245c792016-10-25 02:18:35 -0700233bpf = BPF(text=program, usdt_contexts=[usdt] if usdt else [])
234if args.syscalls:
235 syscall_regex = "^[Ss]y[Ss]_.*"
236 bpf.attach_kprobe(event_re=syscall_regex, fn_name="syscall_entry")
237 if args.latency:
238 bpf.attach_kretprobe(event_re=syscall_regex, fn_name="syscall_return")
239 print("Attached %d kernel probes for syscall tracing." %
240 bpf.num_open_kprobes())
241
242def get_data():
243 # Will be empty when no language was specified for tracing
244 if args.latency:
245 data = map(lambda (k, v): (k.clazz + "." + k.method,
246 (v.num_calls, v.total_ns)),
247 bpf["times"].items())
248 else:
249 data = map(lambda (k, v): (k.clazz + "." + k.method, (v.value, 0)),
250 bpf["counts"].items())
251
252 if args.syscalls:
253 if args.latency:
254 syscalls = map(lambda (k, v): (bpf.ksym(k.value),
255 (v.num_calls, v.total_ns)),
256 bpf["systimes"].items())
257 data.extend(syscalls)
258 else:
259 syscalls = map(lambda (k, v): (bpf.ksym(k.value), (v.value, 0)),
260 bpf["syscounts"].items())
261 data.extend(syscalls)
262
263 return sorted(data, key=lambda (k, v): v[1 if args.latency else 0])
264
265def clear_data():
266 if args.latency:
267 bpf["times"].clear()
268 else:
269 bpf["counts"].clear()
270
271 if args.syscalls:
272 if args.latency:
273 bpf["systimes"].clear()
274 else:
275 bpf["syscounts"].clear()
276
277exit_signaled = False
278print("Tracing calls in process %d (language: %s)... Ctrl-C to quit." %
279 (args.pid, args.language or "none"))
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700280while True:
281 try:
282 sleep(args.interval or 99999999)
283 except KeyboardInterrupt:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700284 exit_signaled = True
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700285 print()
Sasha Goldshteina245c792016-10-25 02:18:35 -0700286 data = get_data() # [(function, (num calls, latency in ns))]
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700287 if args.latency:
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700288 time_col = "TIME (ms)" if args.milliseconds else "TIME (us)"
289 print("%-50s %8s %8s" % ("METHOD", "# CALLS", time_col))
290 else:
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700291 print("%-50s %8s" % ("METHOD", "# CALLS"))
292 if args.top:
293 data = data[-args.top:]
294 for key, value in data:
295 if args.latency:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700296 time = value[1]/1000000.0 if args.milliseconds else \
297 value[1]/1000.0
298 print("%-50s %8d %6.2f" % (key, value[0], time))
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700299 else:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700300 print("%-50s %8d" % (key, value[0]))
301 if args.interval and not exit_signaled:
302 clear_data()
303 else:
304 if args.syscalls:
305 print("Detaching kernel probes, please wait...")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700306 exit()