blob: 99654f26381736ec46e9fe3a7e3505efa6715517 [file] [log] [blame]
Brendan Gregg3e55ae22015-09-10 12:11:35 -07001#!/usr/bin/python
Alexei Starovoitovbdf07732016-01-14 10:09:20 -08002# @lint-avoid-python-3-compatibility-imports
Brendan Gregg3e55ae22015-09-10 12:11:35 -07003#
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -07004# funccount Count functions, tracepoints, and USDT probes.
5# For Linux, uses BCC, eBPF.
Brendan Gregg3e55ae22015-09-10 12:11:35 -07006#
Brendan Greggb03d9eb2017-08-23 15:00:30 -07007# USAGE: funccount [-h] [-p PID] [-i INTERVAL] [-d DURATION] [-T] [-r] pattern
Brendan Gregg3e55ae22015-09-10 12:11:35 -07008#
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -07009# The pattern is a string with optional '*' wildcards, similar to file
10# globbing. If you'd prefer to use regular expressions, use the -r option.
Brendan Gregg3e55ae22015-09-10 12:11:35 -070011#
12# Copyright (c) 2015 Brendan Gregg.
13# Licensed under the Apache License, Version 2.0 (the "License")
14#
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070015# 09-Sep-2015 Brendan Gregg Created this.
16# 18-Oct-2016 Sasha Goldshtein Generalized for uprobes, tracepoints, USDT.
Brendan Gregg3e55ae22015-09-10 12:11:35 -070017
18from __future__ import print_function
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070019from bcc import BPF, USDT
Brendan Gregg3e55ae22015-09-10 12:11:35 -070020from time import sleep, strftime
21import argparse
Junli Ouec615af2016-08-06 11:43:20 +080022import os
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070023import re
24import signal
25import sys
26import traceback
Brendan Gregg3e55ae22015-09-10 12:11:35 -070027
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070028debug = False
Brendan Gregg3e55ae22015-09-10 12:11:35 -070029
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +010030def verify_limit(num):
31 probe_limit = 1000
32 if num > probe_limit:
33 raise Exception("maximum of %d probes allowed, attempted %d" %
34 (probe_limit, num))
35
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070036class Probe(object):
37 def __init__(self, pattern, use_regex=False, pid=None):
38 """Init a new probe.
Brendan Gregg3e55ae22015-09-10 12:11:35 -070039
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070040 Init the probe from the pattern provided by the user. The supported
41 patterns mimic the 'trace' and 'argdist' tools, but are simpler because
42 we don't have to distinguish between probes and retprobes.
Brendan Gregg3e55ae22015-09-10 12:11:35 -070043
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070044 func -- probe a kernel function
45 lib:func -- probe a user-space function in the library 'lib'
Brendan Greggb6035b62017-01-10 17:36:07 -080046 /path:func -- probe a user-space function in binary '/path'
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070047 p::func -- same thing as 'func'
48 p:lib:func -- same thing as 'lib:func'
49 t:cat:event -- probe a kernel tracepoint
50 u:lib:probe -- probe a USDT tracepoint
51 """
52 parts = pattern.split(':')
53 if len(parts) == 1:
54 parts = ["p", "", parts[0]]
55 elif len(parts) == 2:
56 parts = ["p", parts[0], parts[1]]
57 elif len(parts) == 3:
58 if parts[0] == "t":
59 parts = ["t", "", "%s:%s" % tuple(parts[1:])]
60 if parts[0] not in ["p", "t", "u"]:
61 raise Exception("Type must be 'p', 't', or 'u', but got %s" %
62 parts[0])
63 else:
64 raise Exception("Too many ':'-separated components in pattern %s" %
65 pattern)
Brendan Gregg3e55ae22015-09-10 12:11:35 -070066
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070067 (self.type, self.library, self.pattern) = parts
68 if not use_regex:
69 self.pattern = self.pattern.replace('*', '.*')
70 self.pattern = '^' + self.pattern + '$'
71
72 if (self.type == "p" and self.library) or self.type == "u":
73 libpath = BPF.find_library(self.library)
74 if libpath is None:
75 # This might be an executable (e.g. 'bash')
76 libpath = BPF.find_exe(self.library)
77 if libpath is None or len(libpath) == 0:
78 raise Exception("unable to find library %s" % self.library)
79 self.library = libpath
80
81 self.pid = pid
82 self.matched = 0
83 self.trace_functions = {} # map location number to function name
84
85 def is_kernel_probe(self):
86 return self.type == "t" or (self.type == "p" and self.library == "")
87
88 def attach(self):
89 if self.type == "p" and not self.library:
90 for index, function in self.trace_functions.items():
91 self.bpf.attach_kprobe(
92 event=function,
93 fn_name="trace_count_%d" % index,
94 pid=self.pid or -1)
95 elif self.type == "p" and self.library:
96 for index, function in self.trace_functions.items():
97 self.bpf.attach_uprobe(
98 name=self.library,
99 sym=function,
100 fn_name="trace_count_%d" % index,
101 pid=self.pid or -1)
102 elif self.type == "t":
103 for index, function in self.trace_functions.items():
104 self.bpf.attach_tracepoint(
105 tp=function,
106 fn_name="trace_count_%d" % index,
107 pid=self.pid or -1)
108 elif self.type == "u":
109 pass # Nothing to do -- attach already happened in `load`
110
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700111 def _add_function(self, template, probe_name):
112 new_func = "trace_count_%d" % self.matched
113 text = template.replace("PROBE_FUNCTION", new_func)
114 text = text.replace("LOCATION", str(self.matched))
115 self.trace_functions[self.matched] = probe_name
116 self.matched += 1
117 return text
118
119 def _generate_functions(self, template):
120 self.usdt = None
121 text = ""
122 if self.type == "p" and not self.library:
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100123 functions = BPF.get_kprobe_functions(self.pattern)
124 verify_limit(len(functions))
125 for function in functions:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700126 text += self._add_function(template, function)
127 elif self.type == "p" and self.library:
128 # uprobes are tricky because the same function may have multiple
129 # addresses, and the same address may be mapped to multiple
130 # functions. We aren't allowed to create more than one uprobe
131 # per address, so track unique addresses and ignore functions that
132 # map to an address that we've already seen. Also ignore functions
133 # that may repeat multiple times with different addresses.
134 addresses, functions = (set(), set())
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100135 functions_and_addresses = BPF.get_user_functions_and_addresses(
136 self.library, self.pattern)
137 verify_limit(len(functions_and_addresses))
138 for function, address in functions_and_addresses:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700139 if address in addresses or function in functions:
140 continue
141 addresses.add(address)
142 functions.add(function)
143 text += self._add_function(template, function)
144 elif self.type == "t":
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100145 tracepoints = BPF.get_tracepoints(self.pattern)
146 verify_limit(len(tracepoints))
147 for tracepoint in tracepoints:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700148 text += self._add_function(template, tracepoint)
149 elif self.type == "u":
150 self.usdt = USDT(path=self.library, pid=self.pid)
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100151 matches = []
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700152 for probe in self.usdt.enumerate_probes():
153 if not self.pid and (probe.bin_path != self.library):
154 continue
155 if re.match(self.pattern, probe.name):
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100156 matches.append(probe.name)
157 verify_limit(len(matches))
158 for match in matches:
159 new_func = "trace_count_%d" % self.matched
160 text += self._add_function(template, match)
161 self.usdt.enable_probe(match, new_func)
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700162 if debug:
163 print(self.usdt.get_text())
164 return text
165
166 def load(self):
167 trace_count_text = """
168int PROBE_FUNCTION(void *ctx) {
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800169 FILTER
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700170 int loc = LOCATION;
171 u64 *val = counts.lookup(&loc);
172 if (!val) {
173 return 0; // Should never happen, # of locations is known
174 }
Sasha Goldshtein4e9289b2016-10-20 16:19:12 -0700175 (*val)++;
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800176 return 0;
Brendan Gregg3e55ae22015-09-10 12:11:35 -0700177}
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700178 """
179 bpf_text = """#include <uapi/linux/ptrace.h>
Brenden Blanco95728832016-02-23 13:13:57 -0800180
Teng Qin7a3e5bc2017-03-29 13:39:17 -0700181BPF_ARRAY(counts, u64, NUMLOCATIONS);
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700182 """
Brendan Gregg3e55ae22015-09-10 12:11:35 -0700183
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700184 # We really mean the tgid from the kernel's perspective, which is in
185 # the top 32 bits of bpf_get_current_pid_tgid().
186 if self.pid:
187 trace_count_text = trace_count_text.replace('FILTER',
188 """u32 pid = bpf_get_current_pid_tgid() >> 32;
189 if (pid != %d) { return 0; }""" % self.pid)
190 else:
191 trace_count_text = trace_count_text.replace('FILTER', '')
Brendan Gregg3e55ae22015-09-10 12:11:35 -0700192
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700193 bpf_text += self._generate_functions(trace_count_text)
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700194 bpf_text = bpf_text.replace("NUMLOCATIONS",
195 str(len(self.trace_functions)))
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700196 if debug:
197 print(bpf_text)
198
Sasha Goldshtein8e6109b2016-10-28 23:45:08 +0300199 if self.matched == 0:
200 raise Exception("No functions matched by pattern %s" %
201 self.pattern)
202
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700203 self.bpf = BPF(text=bpf_text,
204 usdt_contexts=[self.usdt] if self.usdt else [])
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700205 self.clear() # Initialize all array items to zero
206
207 def counts(self):
208 return self.bpf["counts"]
209
210 def clear(self):
211 counts = self.bpf["counts"]
212 for location, _ in list(self.trace_functions.items()):
213 counts[counts.Key(location)] = counts.Leaf()
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700214
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700215class Tool(object):
216 def __init__(self):
217 examples = """examples:
218 ./funccount 'vfs_*' # count kernel fns starting with "vfs"
219 ./funccount -r '^vfs.*' # same as above, using regular expressions
220 ./funccount -Ti 5 'vfs_*' # output every 5 seconds, with timestamps
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700221 ./funccount -d 10 'vfs_*' # trace for 10 seconds only
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700222 ./funccount -p 185 'vfs_*' # count vfs calls for PID 181 only
223 ./funccount t:sched:sched_fork # count calls to the sched_fork tracepoint
Brendan Greggb6035b62017-01-10 17:36:07 -0800224 ./funccount -p 185 u:node:gc* # count all GC USDT probes in node, PID 185
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700225 ./funccount c:malloc # count all malloc() calls in libc
Brendan Greggb6035b62017-01-10 17:36:07 -0800226 ./funccount go:os.* # count all "os.*" calls in libgo
227 ./funccount -p 185 go:os.* # count all "os.*" calls in libgo, PID 185
228 ./funccount ./test:read* # count "read*" calls in the ./test binary
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700229 """
230 parser = argparse.ArgumentParser(
231 description="Count functions, tracepoints, and USDT probes",
232 formatter_class=argparse.RawDescriptionHelpFormatter,
233 epilog=examples)
234 parser.add_argument("-p", "--pid", type=int,
235 help="trace this PID only")
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700236 parser.add_argument("-i", "--interval",
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700237 help="summary interval, seconds")
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700238 parser.add_argument("-d", "--duration",
239 help="total duration of trace, seconds")
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700240 parser.add_argument("-T", "--timestamp", action="store_true",
241 help="include timestamp on output")
242 parser.add_argument("-r", "--regexp", action="store_true",
243 help="use regular expressions. Default is \"*\" wildcards only.")
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700244 parser.add_argument("-D", "--debug", action="store_true",
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700245 help="print BPF program before starting (for debugging purposes)")
246 parser.add_argument("pattern",
247 help="search expression for events")
248 self.args = parser.parse_args()
249 global debug
250 debug = self.args.debug
251 self.probe = Probe(self.args.pattern, self.args.regexp, self.args.pid)
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700252 if self.args.duration and not self.args.interval:
253 self.args.interval = self.args.duration
254 if not self.args.interval:
255 self.args.interval = 99999999
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700256
257 @staticmethod
258 def _signal_ignore(signal, frame):
259 print()
260
261 def run(self):
262 self.probe.load()
263 self.probe.attach()
264 print("Tracing %d functions for \"%s\"... Hit Ctrl-C to end." %
265 (self.probe.matched, self.args.pattern))
266 exiting = 0 if self.args.interval else 1
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700267 seconds = 0
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700268 while True:
269 try:
270 sleep(int(self.args.interval))
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700271 seconds += int(self.args.interval)
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700272 except KeyboardInterrupt:
273 exiting = 1
274 # as cleanup can take many seconds, trap Ctrl-C:
275 signal.signal(signal.SIGINT, Tool._signal_ignore)
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700276 if self.args.duration and seconds >= int(self.args.duration):
277 exiting = 1
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700278
279 print()
280 if self.args.timestamp:
281 print("%-8s\n" % strftime("%H:%M:%S"), end="")
282
283 print("%-36s %8s" % ("FUNC", "COUNT"))
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700284 counts = self.probe.counts()
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700285 for k, v in sorted(counts.items(),
286 key=lambda counts: counts[1].value):
287 if v.value == 0:
288 continue
289 print("%-36s %8d" %
290 (self.probe.trace_functions[k.value], v.value))
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700291
292 if exiting:
293 print("Detaching...")
294 exit()
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700295 else:
296 self.probe.clear()
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700297
298if __name__ == "__main__":
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800299 try:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700300 Tool().run()
301 except Exception:
302 if debug:
303 traceback.print_exc()
304 elif sys.exc_info()[0] is not SystemExit:
305 print(sys.exc_info()[1])