blob: 396d56eb7a9b9890380d80766ee1c5157b72f4de [file] [log] [blame]
Alexey Ivanovcc01a9c2019-01-16 09:50:46 -08001#!/usr/bin/python
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -07002# @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#
Marko Myllynen9f3662e2018-10-10 21:48:53 +03007# USAGE: ucalls [-l {java,perl,php,python,ruby,tcl}] [-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
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070017from time import sleep
yonghong-songcd9334c2019-02-18 09:08:28 -080018from bcc import BPF, USDT, utils
19from bcc.syscall import syscall_name
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020020
Marko Myllynen9f3662e2018-10-10 21:48:53 +030021languages = ["java", "perl", "php", "python", "ruby", "tcl"]
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070022
23examples = """examples:
Sasha Goldshteina245c792016-10-25 02:18:35 -070024 ./ucalls -l java 185 # trace Java calls and print statistics on ^C
25 ./ucalls -l python 2020 1 # trace Python calls and print every second
26 ./ucalls -l java 185 -S # trace Java calls and syscalls
27 ./ucalls 6712 -S # trace only syscall counts
28 ./ucalls -l ruby 1344 -T 10 # trace top 10 Ruby method calls
29 ./ucalls -l ruby 1344 -L # trace Ruby calls including latency
Sasha Goldshteincfb5ee72017-02-08 14:32:51 -050030 ./ucalls -l php 443 -LS # trace PHP calls and syscalls with latency
Sasha Goldshteina245c792016-10-25 02:18:35 -070031 ./ucalls -l python 2020 -mL # trace Python calls including latency in ms
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070032"""
33parser = argparse.ArgumentParser(
34 description="Summarize method calls in high-level languages.",
35 formatter_class=argparse.RawDescriptionHelpFormatter,
36 epilog=examples)
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070037parser.add_argument("pid", type=int, help="process id to attach to")
38parser.add_argument("interval", type=int, nargs='?',
39 help="print every specified number of seconds")
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020040parser.add_argument("-l", "--language", choices=languages + ["none"],
Sasha Goldshteina245c792016-10-25 02:18:35 -070041 help="language to trace (if none, trace syscalls only)")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070042parser.add_argument("-T", "--top", type=int,
43 help="number of most frequent/slow calls to print")
44parser.add_argument("-L", "--latency", action="store_true",
45 help="record method latency from enter to exit (except recursive calls)")
Sasha Goldshteina245c792016-10-25 02:18:35 -070046parser.add_argument("-S", "--syscalls", action="store_true",
47 help="record syscall latency (adds overhead)")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070048parser.add_argument("-v", "--verbose", action="store_true",
49 help="verbose mode: print the BPF program (for debugging purposes)")
50parser.add_argument("-m", "--milliseconds", action="store_true",
51 help="report times in milliseconds (default is microseconds)")
Marko Myllynen27e7aea2018-09-26 20:09:07 +030052parser.add_argument("--ebpf", action="store_true",
53 help=argparse.SUPPRESS)
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070054args = parser.parse_args()
55
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020056language = args.language
57if not language:
58 language = utils.detect_language(languages, args.pid)
59
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070060# We assume that the entry and return probes have the same arguments. This is
Sasha Goldshteincfb5ee72017-02-08 14:32:51 -050061# the case for Java, Python, Ruby, and PHP. If there's a language where it's
62# not the case, we will need to build a custom correlator from entry to exit.
Geneviève Bastien830c1f72017-07-14 16:04:12 -040063extra_message = ""
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020064if language == "java":
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070065 # TODO for JVM entries, we actually have the real length of the class
Sasha Goldshteina245c792016-10-25 02:18:35 -070066 # and method strings in arg3 and arg5 respectively, so we can insert
67 # the null terminator in its proper position.
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070068 entry_probe = "method__entry"
69 return_probe = "method__return"
70 read_class = "bpf_usdt_readarg(2, ctx, &clazz);"
71 read_method = "bpf_usdt_readarg(4, ctx, &method);"
Paul Chaignonc8b4f672017-10-07 11:51:45 +020072 extra_message = ("If you do not see any results, make sure you ran java"
73 " with option -XX:+ExtendedDTraceProbes")
Marko Myllynen9162be42018-09-04 19:45:16 +030074elif language == "perl":
75 entry_probe = "sub__entry"
76 return_probe = "sub__return"
77 read_class = "bpf_usdt_readarg(2, ctx, &clazz);" # filename really
78 read_method = "bpf_usdt_readarg(1, ctx, &method);"
79elif language == "php":
80 entry_probe = "function__entry"
81 return_probe = "function__return"
82 read_class = "bpf_usdt_readarg(4, ctx, &clazz);"
83 read_method = "bpf_usdt_readarg(1, ctx, &method);"
84 extra_message = ("If you do not see any results, make sure the environment"
85 " variable USE_ZEND_DTRACE is set to 1")
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020086elif language == "python":
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070087 entry_probe = "function__entry"
88 return_probe = "function__return"
89 read_class = "bpf_usdt_readarg(1, ctx, &clazz);" # filename really
90 read_method = "bpf_usdt_readarg(2, ctx, &method);"
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020091elif language == "ruby":
Sasha Goldshtein6e5c6212016-10-25 04:30:54 -070092 # TODO Also probe cmethod__entry and cmethod__return with same arguments
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -070093 entry_probe = "method__entry"
94 return_probe = "method__return"
95 read_class = "bpf_usdt_readarg(1, ctx, &clazz);"
96 read_method = "bpf_usdt_readarg(2, ctx, &method);"
Marko Myllynen9f3662e2018-10-10 21:48:53 +030097elif language == "tcl":
98 # TODO Also consider probe cmd__entry and cmd__return with same arguments
99 entry_probe = "proc__entry"
100 return_probe = "proc__return"
101 read_class = "" # no class/file info available
102 read_method = "bpf_usdt_readarg(1, ctx, &method);"
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200103elif not language or language == "none":
Sasha Goldshteina245c792016-10-25 02:18:35 -0700104 if not args.syscalls:
105 print("Nothing to do; use -S to trace syscalls.")
106 exit(1)
107 entry_probe, return_probe, read_class, read_method = ("", "", "", "")
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200108 if language:
109 language = None
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700110
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700111program = """
Sasha Goldshteina245c792016-10-25 02:18:35 -0700112#include <linux/ptrace.h>
113
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700114#define MAX_STRING_LENGTH 80
Sasha Goldshteina245c792016-10-25 02:18:35 -0700115DEFINE_NOLANG
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700116DEFINE_LATENCY
Sasha Goldshteina245c792016-10-25 02:18:35 -0700117DEFINE_SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700118
119struct method_t {
120 char clazz[MAX_STRING_LENGTH];
121 char method[MAX_STRING_LENGTH];
122};
123struct entry_t {
124 u64 pid;
125 struct method_t method;
126};
127struct info_t {
128 u64 num_calls;
129 u64 total_ns;
130};
Sasha Goldshteina245c792016-10-25 02:18:35 -0700131struct syscall_entry_t {
132 u64 timestamp;
William Cohen218f7482018-12-06 13:41:01 -0500133 u64 id;
Sasha Goldshteina245c792016-10-25 02:18:35 -0700134};
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700135
136#ifndef LATENCY
Sasha Goldshteina245c792016-10-25 02:18:35 -0700137 BPF_HASH(counts, struct method_t, u64); // number of calls
138 #ifdef SYSCALLS
139 BPF_HASH(syscounts, u64, u64); // number of calls per IP
140 #endif // SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700141#else
Sasha Goldshteina245c792016-10-25 02:18:35 -0700142 BPF_HASH(times, struct method_t, struct info_t);
143 BPF_HASH(entry, struct entry_t, u64); // timestamp at entry
144 #ifdef SYSCALLS
145 BPF_HASH(systimes, u64, struct info_t); // latency per IP
146 BPF_HASH(sysentry, u64, struct syscall_entry_t); // ts + IP at entry
147 #endif // SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700148#endif
149
Sasha Goldshteina245c792016-10-25 02:18:35 -0700150#ifndef NOLANG
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700151int trace_entry(struct pt_regs *ctx) {
152 u64 clazz = 0, method = 0, val = 0;
153 u64 *valp;
154 struct entry_t data = {0};
155#ifdef LATENCY
156 u64 timestamp = bpf_ktime_get_ns();
157 data.pid = bpf_get_current_pid_tgid();
158#endif
159 READ_CLASS
160 READ_METHOD
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500161 bpf_probe_read_user(&data.method.clazz, sizeof(data.method.clazz),
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700162 (void *)clazz);
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500163 bpf_probe_read_user(&data.method.method, sizeof(data.method.method),
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700164 (void *)method);
165#ifndef LATENCY
yonghong-song82f43022019-10-31 08:16:12 -0700166 valp = counts.lookup_or_try_init(&data.method, &val);
Philip Gladstoneba64f032019-09-20 01:12:01 -0400167 if (valp) {
168 ++(*valp);
169 }
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700170#endif
171#ifdef LATENCY
172 entry.update(&data, &timestamp);
173#endif
174 return 0;
175}
176
177#ifdef LATENCY
178int trace_return(struct pt_regs *ctx) {
179 u64 *entry_timestamp, clazz = 0, method = 0;
180 struct info_t *info, zero = {};
181 struct entry_t data = {};
182 data.pid = bpf_get_current_pid_tgid();
183 READ_CLASS
184 READ_METHOD
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500185 bpf_probe_read_user(&data.method.clazz, sizeof(data.method.clazz),
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700186 (void *)clazz);
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500187 bpf_probe_read_user(&data.method.method, sizeof(data.method.method),
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700188 (void *)method);
189 entry_timestamp = entry.lookup(&data);
190 if (!entry_timestamp) {
191 return 0; // missed the entry event
192 }
yonghong-song82f43022019-10-31 08:16:12 -0700193 info = times.lookup_or_try_init(&data.method, &zero);
Philip Gladstoneba64f032019-09-20 01:12:01 -0400194 if (info) {
195 info->num_calls += 1;
196 info->total_ns += bpf_ktime_get_ns() - *entry_timestamp;
197 }
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700198 entry.delete(&data);
199 return 0;
200}
Sasha Goldshteina245c792016-10-25 02:18:35 -0700201#endif // LATENCY
202#endif // NOLANG
203
204#ifdef SYSCALLS
William Cohen218f7482018-12-06 13:41:01 -0500205TRACEPOINT_PROBE(raw_syscalls, sys_enter) {
Sasha Goldshteina245c792016-10-25 02:18:35 -0700206 u64 pid = bpf_get_current_pid_tgid();
William Cohen218f7482018-12-06 13:41:01 -0500207 u64 *valp, id = args->id, val = 0;
Sasha Goldshteina245c792016-10-25 02:18:35 -0700208 PID_FILTER
209#ifdef LATENCY
210 struct syscall_entry_t data = {};
211 data.timestamp = bpf_ktime_get_ns();
William Cohen218f7482018-12-06 13:41:01 -0500212 data.id = id;
213 sysentry.update(&pid, &data);
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700214#endif
Sasha Goldshteina245c792016-10-25 02:18:35 -0700215#ifndef LATENCY
yonghong-song82f43022019-10-31 08:16:12 -0700216 valp = syscounts.lookup_or_try_init(&id, &val);
Philip Gladstoneba64f032019-09-20 01:12:01 -0400217 if (valp) {
218 ++(*valp);
219 }
Sasha Goldshteina245c792016-10-25 02:18:35 -0700220#endif
Sasha Goldshteina245c792016-10-25 02:18:35 -0700221 return 0;
222}
223
224#ifdef LATENCY
William Cohen218f7482018-12-06 13:41:01 -0500225TRACEPOINT_PROBE(raw_syscalls, sys_exit) {
Sasha Goldshteina245c792016-10-25 02:18:35 -0700226 struct syscall_entry_t *e;
227 struct info_t *info, zero = {};
William Cohen218f7482018-12-06 13:41:01 -0500228 u64 pid = bpf_get_current_pid_tgid(), id;
Sasha Goldshteina245c792016-10-25 02:18:35 -0700229 PID_FILTER
230 e = sysentry.lookup(&pid);
231 if (!e) {
232 return 0; // missed the entry event
233 }
William Cohen218f7482018-12-06 13:41:01 -0500234 id = e->id;
yonghong-song82f43022019-10-31 08:16:12 -0700235 info = systimes.lookup_or_try_init(&id, &zero);
Philip Gladstoneba64f032019-09-20 01:12:01 -0400236 if (info) {
237 info->num_calls += 1;
238 info->total_ns += bpf_ktime_get_ns() - e->timestamp;
239 }
Sasha Goldshteina245c792016-10-25 02:18:35 -0700240 sysentry.delete(&pid);
241 return 0;
242}
243#endif // LATENCY
244#endif // SYSCALLS
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700245""".replace("READ_CLASS", read_class) \
246 .replace("READ_METHOD", read_method) \
Sasha Goldshteina245c792016-10-25 02:18:35 -0700247 .replace("PID_FILTER", "if ((pid >> 32) != %d) { return 0; }" % args.pid) \
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200248 .replace("DEFINE_NOLANG", "#define NOLANG" if not language else "") \
Sasha Goldshteina245c792016-10-25 02:18:35 -0700249 .replace("DEFINE_LATENCY", "#define LATENCY" if args.latency else "") \
250 .replace("DEFINE_SYSCALLS", "#define SYSCALLS" if args.syscalls else "")
251
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200252if language:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700253 usdt = USDT(pid=args.pid)
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -0500254 usdt.enable_probe_or_bail(entry_probe, "trace_entry")
Sasha Goldshteina245c792016-10-25 02:18:35 -0700255 if args.latency:
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -0500256 usdt.enable_probe_or_bail(return_probe, "trace_return")
Sasha Goldshteina245c792016-10-25 02:18:35 -0700257else:
258 usdt = None
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700259
Marko Myllynen27e7aea2018-09-26 20:09:07 +0300260if args.ebpf or args.verbose:
261 if args.verbose and usdt:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700262 print(usdt.get_text())
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700263 print(program)
Marko Myllynen27e7aea2018-09-26 20:09:07 +0300264 if args.ebpf:
265 exit()
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700266
Sasha Goldshteina245c792016-10-25 02:18:35 -0700267bpf = BPF(text=program, usdt_contexts=[usdt] if usdt else [])
268if args.syscalls:
William Cohen218f7482018-12-06 13:41:01 -0500269 print("Attached kernel tracepoints for syscall tracing.")
Sasha Goldshteina245c792016-10-25 02:18:35 -0700270
271def get_data():
272 # Will be empty when no language was specified for tracing
273 if args.latency:
jeromemarchand4e4c9e02018-07-19 22:20:54 +0200274 data = list(map(lambda kv: (kv[0].clazz.decode('utf-8', 'replace') \
275 + "." + \
276 kv[0].method.decode('utf-8', 'replace'),
Rafael Fonseca0d669062017-02-13 15:52:04 +0100277 (kv[1].num_calls, kv[1].total_ns)),
Rafael Fonseca42900ae2017-02-13 15:46:54 +0100278 bpf["times"].items()))
Sasha Goldshteina245c792016-10-25 02:18:35 -0700279 else:
jeromemarchand4e4c9e02018-07-19 22:20:54 +0200280 data = list(map(lambda kv: (kv[0].clazz.decode('utf-8', 'replace') \
281 + "." + \
282 kv[0].method.decode('utf-8', 'replace'),
Rafael Fonseca0d669062017-02-13 15:52:04 +0100283 (kv[1].value, 0)),
Rafael Fonseca42900ae2017-02-13 15:46:54 +0100284 bpf["counts"].items()))
Sasha Goldshteina245c792016-10-25 02:18:35 -0700285
286 if args.syscalls:
287 if args.latency:
William Cohen218f7482018-12-06 13:41:01 -0500288 syscalls = map(lambda kv: (syscall_name(kv[0].value).decode('utf-8', 'replace'),
289 (kv[1].num_calls, kv[1].total_ns)),
Sasha Goldshteina245c792016-10-25 02:18:35 -0700290 bpf["systimes"].items())
291 data.extend(syscalls)
292 else:
William Cohen218f7482018-12-06 13:41:01 -0500293 syscalls = map(lambda kv: (syscall_name(kv[0].value).decode('utf-8', 'replace'),
Paul Chaignon956ca1c2017-03-04 20:07:56 +0100294 (kv[1].value, 0)),
Sasha Goldshteina245c792016-10-25 02:18:35 -0700295 bpf["syscounts"].items())
296 data.extend(syscalls)
297
Rafael Fonseca0d669062017-02-13 15:52:04 +0100298 return sorted(data, key=lambda kv: kv[1][1 if args.latency else 0])
Sasha Goldshteina245c792016-10-25 02:18:35 -0700299
300def clear_data():
301 if args.latency:
302 bpf["times"].clear()
303 else:
304 bpf["counts"].clear()
305
306 if args.syscalls:
307 if args.latency:
308 bpf["systimes"].clear()
309 else:
310 bpf["syscounts"].clear()
311
312exit_signaled = False
313print("Tracing calls in process %d (language: %s)... Ctrl-C to quit." %
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200314 (args.pid, language or "none"))
Geneviève Bastien830c1f72017-07-14 16:04:12 -0400315if extra_message:
316 print(extra_message)
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700317while True:
318 try:
319 sleep(args.interval or 99999999)
320 except KeyboardInterrupt:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700321 exit_signaled = True
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700322 print()
Sasha Goldshteina245c792016-10-25 02:18:35 -0700323 data = get_data() # [(function, (num calls, latency in ns))]
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700324 if args.latency:
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700325 time_col = "TIME (ms)" if args.milliseconds else "TIME (us)"
326 print("%-50s %8s %8s" % ("METHOD", "# CALLS", time_col))
327 else:
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700328 print("%-50s %8s" % ("METHOD", "# CALLS"))
329 if args.top:
330 data = data[-args.top:]
331 for key, value in data:
332 if args.latency:
Paul Chaignon956ca1c2017-03-04 20:07:56 +0100333 time = value[1] / 1000000.0 if args.milliseconds else \
334 value[1] / 1000.0
Sasha Goldshteina245c792016-10-25 02:18:35 -0700335 print("%-50s %8d %6.2f" % (key, value[0], time))
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700336 else:
Sasha Goldshteina245c792016-10-25 02:18:35 -0700337 print("%-50s %8d" % (key, value[0]))
338 if args.interval and not exit_signaled:
339 clear_data()
340 else:
341 if args.syscalls:
342 print("Detaching kernel probes, please wait...")
Sasha Goldshteinc13d14f2016-10-17 04:13:48 -0700343 exit()