blob: 19bab0bef0838b3e6f03e1f0a095525d3ba648d6 [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 Goldshteina245c792016-10-25 02:18:35 -07007# USAGE: ucalls [-l {java,python,ruby}] [-h] [-T TOP] [-L] [-S] [-v] [-m]
8# 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
27 ./ucalls -l ruby 1344 -LS # trace Ruby calls and syscalls with latency
28 ./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 Goldshteina245c792016-10-25 02:18:35 -070037parser.add_argument("-l", "--language", choices=["java", "python", "ruby"],
38 help="language to trace (if none, trace syscalls only)")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070039parser.add_argument("-T", "--top", type=int,
40 help="number of most frequent/slow calls to print")
41parser.add_argument("-L", "--latency", action="store_true",
42 help="record method latency from enter to exit (except recursive calls)")
Sasha Goldshteina245c792016-10-25 02:18:35 -070043parser.add_argument("-S", "--syscalls", action="store_true",
44 help="record syscall latency (adds overhead)")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070045parser.add_argument("-v", "--verbose", action="store_true",
46 help="verbose mode: print the BPF program (for debugging purposes)")
47parser.add_argument("-m", "--milliseconds", action="store_true",
48 help="report times in milliseconds (default is microseconds)")
49args = parser.parse_args()
50
51# We assume that the entry and return probes have the same arguments. This is
52# the case for Java, Python, and Ruby. If there's a language where it's not the
53# case, we will need to build a custom correlator from entry to exit.
54if args.language == "java":
55 # TODO for JVM entries, we actually have the real length of the class
Sasha Goldshteina245c792016-10-25 02:18:35 -070056 # and method strings in arg3 and arg5 respectively, so we can insert
57 # the null terminator in its proper position.
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070058 entry_probe = "method__entry"
59 return_probe = "method__return"
60 read_class = "bpf_usdt_readarg(2, ctx, &clazz);"
61 read_method = "bpf_usdt_readarg(4, ctx, &method);"
62elif args.language == "python":
63 entry_probe = "function__entry"
64 return_probe = "function__return"
65 read_class = "bpf_usdt_readarg(1, ctx, &clazz);" # filename really
66 read_method = "bpf_usdt_readarg(2, ctx, &method);"
67elif args.language == "ruby":
68 entry_probe = "method__entry"
69 return_probe = "method__return"
70 read_class = "bpf_usdt_readarg(1, ctx, &clazz);"
71 read_method = "bpf_usdt_readarg(2, ctx, &method);"
Sasha Goldshteina245c792016-10-25 02:18:35 -070072elif not args.language:
73 if not args.syscalls:
74 print("Nothing to do; use -S to trace syscalls.")
75 exit(1)
76 entry_probe, return_probe, read_class, read_method = ("", "", "", "")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070077
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070078program = """
Sasha Goldshteina245c792016-10-25 02:18:35 -070079#include <linux/ptrace.h>
80
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070081#define MAX_STRING_LENGTH 80
Sasha Goldshteina245c792016-10-25 02:18:35 -070082DEFINE_NOLANG
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070083DEFINE_LATENCY
Sasha Goldshteina245c792016-10-25 02:18:35 -070084DEFINE_SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070085
86struct method_t {
87 char clazz[MAX_STRING_LENGTH];
88 char method[MAX_STRING_LENGTH];
89};
90struct entry_t {
91 u64 pid;
92 struct method_t method;
93};
94struct info_t {
95 u64 num_calls;
96 u64 total_ns;
97};
Sasha Goldshteina245c792016-10-25 02:18:35 -070098struct syscall_entry_t {
99 u64 timestamp;
100 u64 ip;
101};
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700102
103#ifndef LATENCY
Sasha Goldshteina245c792016-10-25 02:18:35 -0700104 BPF_HASH(counts, struct method_t, u64); // number of calls
105 #ifdef SYSCALLS
106 BPF_HASH(syscounts, u64, u64); // number of calls per IP
107 #endif // SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700108#else
Sasha Goldshteina245c792016-10-25 02:18:35 -0700109 BPF_HASH(times, struct method_t, struct info_t);
110 BPF_HASH(entry, struct entry_t, u64); // timestamp at entry
111 #ifdef SYSCALLS
112 BPF_HASH(systimes, u64, struct info_t); // latency per IP
113 BPF_HASH(sysentry, u64, struct syscall_entry_t); // ts + IP at entry
114 #endif // SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700115#endif
116
Sasha Goldshteina245c792016-10-25 02:18:35 -0700117#ifndef NOLANG
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700118int trace_entry(struct pt_regs *ctx) {
119 u64 clazz = 0, method = 0, val = 0;
120 u64 *valp;
121 struct entry_t data = {0};
122#ifdef LATENCY
123 u64 timestamp = bpf_ktime_get_ns();
124 data.pid = bpf_get_current_pid_tgid();
125#endif
126 READ_CLASS
127 READ_METHOD
128 bpf_probe_read(&data.method.clazz, sizeof(data.method.clazz),
129 (void *)clazz);
130 bpf_probe_read(&data.method.method, sizeof(data.method.method),
131 (void *)method);
132#ifndef LATENCY
133 valp = counts.lookup_or_init(&data.method, &val);
134 ++(*valp);
135#endif
136#ifdef LATENCY
137 entry.update(&data, &timestamp);
138#endif
139 return 0;
140}
141
142#ifdef LATENCY
143int trace_return(struct pt_regs *ctx) {
144 u64 *entry_timestamp, clazz = 0, method = 0;
145 struct info_t *info, zero = {};
146 struct entry_t data = {};
147 data.pid = bpf_get_current_pid_tgid();
148 READ_CLASS
149 READ_METHOD
150 bpf_probe_read(&data.method.clazz, sizeof(data.method.clazz),
151 (void *)clazz);
152 bpf_probe_read(&data.method.method, sizeof(data.method.method),
153 (void *)method);
154 entry_timestamp = entry.lookup(&data);
155 if (!entry_timestamp) {
156 return 0; // missed the entry event
157 }
158 info = times.lookup_or_init(&data.method, &zero);
159 info->num_calls += 1;
160 info->total_ns += bpf_ktime_get_ns() - *entry_timestamp;
161 entry.delete(&data);
162 return 0;
163}
Sasha Goldshteina245c792016-10-25 02:18:35 -0700164#endif // LATENCY
165#endif // NOLANG
166
167#ifdef SYSCALLS
168int syscall_entry(struct pt_regs *ctx) {
169 u64 pid = bpf_get_current_pid_tgid();
170 u64 *valp, ip = ctx->ip, val = 0;
171 PID_FILTER
172#ifdef LATENCY
173 struct syscall_entry_t data = {};
174 data.timestamp = bpf_ktime_get_ns();
175 data.ip = ip;
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700176#endif
Sasha Goldshteina245c792016-10-25 02:18:35 -0700177#ifndef LATENCY
178 valp = syscounts.lookup_or_init(&ip, &val);
179 ++(*valp);
180#endif
181#ifdef LATENCY
182 sysentry.update(&pid, &data);
183#endif
184 return 0;
185}
186
187#ifdef LATENCY
188int syscall_return(struct pt_regs *ctx) {
189 struct syscall_entry_t *e;
190 struct info_t *info, zero = {};
191 u64 pid = bpf_get_current_pid_tgid(), ip;
192 PID_FILTER
193 e = sysentry.lookup(&pid);
194 if (!e) {
195 return 0; // missed the entry event
196 }
197 ip = e->ip;
198 info = systimes.lookup_or_init(&ip, &zero);
199 info->num_calls += 1;
200 info->total_ns += bpf_ktime_get_ns() - e->timestamp;
201 sysentry.delete(&pid);
202 return 0;
203}
204#endif // LATENCY
205#endif // SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700206""".replace("READ_CLASS", read_class) \
207 .replace("READ_METHOD", read_method) \
Sasha Goldshteina245c792016-10-25 02:18:35 -0700208 .replace("PID_FILTER", "if ((pid >> 32) != %d) { return 0; }" % args.pid) \
209 .replace("DEFINE_NOLANG", "#define NOLANG" if not args.language else "") \
210 .replace("DEFINE_LATENCY", "#define LATENCY" if args.latency else "") \
211 .replace("DEFINE_SYSCALLS", "#define SYSCALLS" if args.syscalls else "")
212
213if args.language:
214 usdt = USDT(pid=args.pid)
215 usdt.enable_probe(entry_probe, "trace_entry")
216 if args.latency:
217 usdt.enable_probe(return_probe, "trace_return")
218else:
219 usdt = None
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700220
221if args.verbose:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700222 if usdt:
223 print(usdt.get_text())
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700224 print(program)
225
Sasha Goldshteina245c792016-10-25 02:18:35 -0700226bpf = BPF(text=program, usdt_contexts=[usdt] if usdt else [])
227if args.syscalls:
228 syscall_regex = "^[Ss]y[Ss]_.*"
229 bpf.attach_kprobe(event_re=syscall_regex, fn_name="syscall_entry")
230 if args.latency:
231 bpf.attach_kretprobe(event_re=syscall_regex, fn_name="syscall_return")
232 print("Attached %d kernel probes for syscall tracing." %
233 bpf.num_open_kprobes())
234
235def get_data():
236 # Will be empty when no language was specified for tracing
237 if args.latency:
238 data = map(lambda (k, v): (k.clazz + "." + k.method,
239 (v.num_calls, v.total_ns)),
240 bpf["times"].items())
241 else:
242 data = map(lambda (k, v): (k.clazz + "." + k.method, (v.value, 0)),
243 bpf["counts"].items())
244
245 if args.syscalls:
246 if args.latency:
247 syscalls = map(lambda (k, v): (bpf.ksym(k.value),
248 (v.num_calls, v.total_ns)),
249 bpf["systimes"].items())
250 data.extend(syscalls)
251 else:
252 syscalls = map(lambda (k, v): (bpf.ksym(k.value), (v.value, 0)),
253 bpf["syscounts"].items())
254 data.extend(syscalls)
255
256 return sorted(data, key=lambda (k, v): v[1 if args.latency else 0])
257
258def clear_data():
259 if args.latency:
260 bpf["times"].clear()
261 else:
262 bpf["counts"].clear()
263
264 if args.syscalls:
265 if args.latency:
266 bpf["systimes"].clear()
267 else:
268 bpf["syscounts"].clear()
269
270exit_signaled = False
271print("Tracing calls in process %d (language: %s)... Ctrl-C to quit." %
272 (args.pid, args.language or "none"))
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700273while True:
274 try:
275 sleep(args.interval or 99999999)
276 except KeyboardInterrupt:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700277 exit_signaled = True
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700278 print()
Sasha Goldshteina245c792016-10-25 02:18:35 -0700279 data = get_data() # [(function, (num calls, latency in ns))]
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700280 if args.latency:
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700281 time_col = "TIME (ms)" if args.milliseconds else "TIME (us)"
282 print("%-50s %8s %8s" % ("METHOD", "# CALLS", time_col))
283 else:
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700284 print("%-50s %8s" % ("METHOD", "# CALLS"))
285 if args.top:
286 data = data[-args.top:]
287 for key, value in data:
288 if args.latency:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700289 time = value[1]/1000000.0 if args.milliseconds else \
290 value[1]/1000.0
291 print("%-50s %8d %6.2f" % (key, value[0], time))
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700292 else:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700293 print("%-50s %8d" % (key, value[0]))
294 if args.interval and not exit_signaled:
295 clear_data()
296 else:
297 if args.syscalls:
298 print("Detaching kernel probes, please wait...")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700299 exit()