blob: 69dd01c8cada441bc122030f90911deecd8c85e9 [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
Brenden Blanco42d60982017-04-24 14:31:28 -070019from bcc import ArgString, 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 """
Brenden Blanco42d60982017-04-24 14:31:28 -070052 parts = bytes(pattern).split(b':')
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070053 if len(parts) == 1:
Brenden Blanco42d60982017-04-24 14:31:28 -070054 parts = [b"p", b"", parts[0]]
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070055 elif len(parts) == 2:
Brenden Blanco42d60982017-04-24 14:31:28 -070056 parts = [b"p", parts[0], parts[1]]
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070057 elif len(parts) == 3:
Brenden Blanco42d60982017-04-24 14:31:28 -070058 if parts[0] == b"t":
59 parts = [b"t", b"", b"%s:%s" % tuple(parts[1:])]
60 if parts[0] not in [b"p", b"t", b"u"]:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070061 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:
Brenden Blanco42d60982017-04-24 14:31:28 -070069 self.pattern = self.pattern.replace(b'*', b'.*')
70 self.pattern = b'^' + self.pattern + b'$'
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070071
Brenden Blanco42d60982017-04-24 14:31:28 -070072 if (self.type == b"p" and self.library) or self.type == b"u":
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070073 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):
Brenden Blanco42d60982017-04-24 14:31:28 -070086 return self.type == b"t" or (self.type == b"p" and self.library == b"")
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070087
88 def attach(self):
Brenden Blanco42d60982017-04-24 14:31:28 -070089 if self.type == b"p" and not self.library:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070090 for index, function in self.trace_functions.items():
91 self.bpf.attach_kprobe(
92 event=function,
Teng Qinfd244052017-12-15 15:53:53 -080093 fn_name="trace_count_%d" % index)
Brenden Blanco42d60982017-04-24 14:31:28 -070094 elif self.type == b"p" and self.library:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -070095 for index, function in self.trace_functions.items():
96 self.bpf.attach_uprobe(
97 name=self.library,
98 sym=function,
99 fn_name="trace_count_%d" % index,
100 pid=self.pid or -1)
Brenden Blanco42d60982017-04-24 14:31:28 -0700101 elif self.type == b"t":
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700102 for index, function in self.trace_functions.items():
103 self.bpf.attach_tracepoint(
104 tp=function,
Teng Qinfd244052017-12-15 15:53:53 -0800105 fn_name="trace_count_%d" % index)
Brenden Blanco42d60982017-04-24 14:31:28 -0700106 elif self.type == b"u":
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700107 pass # Nothing to do -- attach already happened in `load`
108
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700109 def _add_function(self, template, probe_name):
Brenden Blanco42d60982017-04-24 14:31:28 -0700110 new_func = b"trace_count_%d" % self.matched
111 text = template.replace(b"PROBE_FUNCTION", new_func)
112 text = text.replace(b"LOCATION", b"%d" % self.matched)
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700113 self.trace_functions[self.matched] = probe_name
114 self.matched += 1
115 return text
116
117 def _generate_functions(self, template):
118 self.usdt = None
Brenden Blanco42d60982017-04-24 14:31:28 -0700119 text = b""
120 if self.type == b"p" and not self.library:
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100121 functions = BPF.get_kprobe_functions(self.pattern)
122 verify_limit(len(functions))
123 for function in functions:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700124 text += self._add_function(template, function)
Brenden Blanco42d60982017-04-24 14:31:28 -0700125 elif self.type == b"p" and self.library:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700126 # uprobes are tricky because the same function may have multiple
127 # addresses, and the same address may be mapped to multiple
128 # functions. We aren't allowed to create more than one uprobe
129 # per address, so track unique addresses and ignore functions that
130 # map to an address that we've already seen. Also ignore functions
131 # that may repeat multiple times with different addresses.
132 addresses, functions = (set(), set())
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100133 functions_and_addresses = BPF.get_user_functions_and_addresses(
134 self.library, self.pattern)
135 verify_limit(len(functions_and_addresses))
136 for function, address in functions_and_addresses:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700137 if address in addresses or function in functions:
138 continue
139 addresses.add(address)
140 functions.add(function)
141 text += self._add_function(template, function)
Brenden Blanco42d60982017-04-24 14:31:28 -0700142 elif self.type == b"t":
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100143 tracepoints = BPF.get_tracepoints(self.pattern)
144 verify_limit(len(tracepoints))
145 for tracepoint in tracepoints:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700146 text += self._add_function(template, tracepoint)
Brenden Blanco42d60982017-04-24 14:31:28 -0700147 elif self.type == b"u":
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700148 self.usdt = USDT(path=self.library, pid=self.pid)
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100149 matches = []
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700150 for probe in self.usdt.enumerate_probes():
151 if not self.pid and (probe.bin_path != self.library):
152 continue
153 if re.match(self.pattern, probe.name):
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100154 matches.append(probe.name)
155 verify_limit(len(matches))
156 for match in matches:
Brenden Blanco42d60982017-04-24 14:31:28 -0700157 new_func = b"trace_count_%d" % self.matched
Sasha Goldshtein3ba14ef2016-10-20 02:46:37 +0100158 text += self._add_function(template, match)
159 self.usdt.enable_probe(match, new_func)
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700160 if debug:
161 print(self.usdt.get_text())
162 return text
163
164 def load(self):
Brenden Blanco42d60982017-04-24 14:31:28 -0700165 trace_count_text = b"""
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700166int PROBE_FUNCTION(void *ctx) {
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800167 FILTER
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700168 int loc = LOCATION;
169 u64 *val = counts.lookup(&loc);
170 if (!val) {
171 return 0; // Should never happen, # of locations is known
172 }
Sasha Goldshtein4e9289b2016-10-20 16:19:12 -0700173 (*val)++;
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800174 return 0;
Brendan Gregg3e55ae22015-09-10 12:11:35 -0700175}
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700176 """
Brenden Blanco42d60982017-04-24 14:31:28 -0700177 bpf_text = b"""#include <uapi/linux/ptrace.h>
Brenden Blanco95728832016-02-23 13:13:57 -0800178
Teng Qin7a3e5bc2017-03-29 13:39:17 -0700179BPF_ARRAY(counts, u64, NUMLOCATIONS);
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700180 """
Brendan Gregg3e55ae22015-09-10 12:11:35 -0700181
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700182 # We really mean the tgid from the kernel's perspective, which is in
183 # the top 32 bits of bpf_get_current_pid_tgid().
184 if self.pid:
Brenden Blanco42d60982017-04-24 14:31:28 -0700185 trace_count_text = trace_count_text.replace(b'FILTER',
186 b"""u32 pid = bpf_get_current_pid_tgid() >> 32;
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700187 if (pid != %d) { return 0; }""" % self.pid)
188 else:
Brenden Blanco42d60982017-04-24 14:31:28 -0700189 trace_count_text = trace_count_text.replace(b'FILTER', b'')
Brendan Gregg3e55ae22015-09-10 12:11:35 -0700190
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700191 bpf_text += self._generate_functions(trace_count_text)
Brenden Blanco42d60982017-04-24 14:31:28 -0700192 bpf_text = bpf_text.replace(b"NUMLOCATIONS",
193 b"%d" % len(self.trace_functions))
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700194 if debug:
195 print(bpf_text)
196
Sasha Goldshtein8e6109b2016-10-28 23:45:08 +0300197 if self.matched == 0:
198 raise Exception("No functions matched by pattern %s" %
199 self.pattern)
200
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700201 self.bpf = BPF(text=bpf_text,
202 usdt_contexts=[self.usdt] if self.usdt else [])
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700203 self.clear() # Initialize all array items to zero
204
205 def counts(self):
206 return self.bpf["counts"]
207
208 def clear(self):
209 counts = self.bpf["counts"]
210 for location, _ in list(self.trace_functions.items()):
211 counts[counts.Key(location)] = counts.Leaf()
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700212
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700213class Tool(object):
214 def __init__(self):
215 examples = """examples:
216 ./funccount 'vfs_*' # count kernel fns starting with "vfs"
217 ./funccount -r '^vfs.*' # same as above, using regular expressions
218 ./funccount -Ti 5 'vfs_*' # output every 5 seconds, with timestamps
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700219 ./funccount -d 10 'vfs_*' # trace for 10 seconds only
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700220 ./funccount -p 185 'vfs_*' # count vfs calls for PID 181 only
221 ./funccount t:sched:sched_fork # count calls to the sched_fork tracepoint
Brendan Greggb6035b62017-01-10 17:36:07 -0800222 ./funccount -p 185 u:node:gc* # count all GC USDT probes in node, PID 185
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700223 ./funccount c:malloc # count all malloc() calls in libc
Brendan Greggb6035b62017-01-10 17:36:07 -0800224 ./funccount go:os.* # count all "os.*" calls in libgo
225 ./funccount -p 185 go:os.* # count all "os.*" calls in libgo, PID 185
226 ./funccount ./test:read* # count "read*" calls in the ./test binary
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700227 """
228 parser = argparse.ArgumentParser(
229 description="Count functions, tracepoints, and USDT probes",
230 formatter_class=argparse.RawDescriptionHelpFormatter,
231 epilog=examples)
232 parser.add_argument("-p", "--pid", type=int,
233 help="trace this PID only")
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700234 parser.add_argument("-i", "--interval",
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700235 help="summary interval, seconds")
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700236 parser.add_argument("-d", "--duration",
237 help="total duration of trace, seconds")
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700238 parser.add_argument("-T", "--timestamp", action="store_true",
239 help="include timestamp on output")
240 parser.add_argument("-r", "--regexp", action="store_true",
241 help="use regular expressions. Default is \"*\" wildcards only.")
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700242 parser.add_argument("-D", "--debug", action="store_true",
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700243 help="print BPF program before starting (for debugging purposes)")
244 parser.add_argument("pattern",
Brenden Blanco42d60982017-04-24 14:31:28 -0700245 type=ArgString,
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700246 help="search expression for events")
247 self.args = parser.parse_args()
248 global debug
249 debug = self.args.debug
250 self.probe = Probe(self.args.pattern, self.args.regexp, self.args.pid)
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700251 if self.args.duration and not self.args.interval:
252 self.args.interval = self.args.duration
253 if not self.args.interval:
254 self.args.interval = 99999999
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700255
256 @staticmethod
257 def _signal_ignore(signal, frame):
258 print()
259
260 def run(self):
261 self.probe.load()
262 self.probe.attach()
263 print("Tracing %d functions for \"%s\"... Hit Ctrl-C to end." %
Brenden Blanco42d60982017-04-24 14:31:28 -0700264 (self.probe.matched, bytes(self.args.pattern)))
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700265 exiting = 0 if self.args.interval else 1
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700266 seconds = 0
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700267 while True:
268 try:
269 sleep(int(self.args.interval))
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700270 seconds += int(self.args.interval)
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700271 except KeyboardInterrupt:
272 exiting = 1
273 # as cleanup can take many seconds, trap Ctrl-C:
274 signal.signal(signal.SIGINT, Tool._signal_ignore)
Brendan Greggb03d9eb2017-08-23 15:00:30 -0700275 if self.args.duration and seconds >= int(self.args.duration):
276 exiting = 1
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700277
278 print()
279 if self.args.timestamp:
280 print("%-8s\n" % strftime("%H:%M:%S"), end="")
281
282 print("%-36s %8s" % ("FUNC", "COUNT"))
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700283 counts = self.probe.counts()
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700284 for k, v in sorted(counts.items(),
285 key=lambda counts: counts[1].value):
286 if v.value == 0:
287 continue
288 print("%-36s %8d" %
289 (self.probe.trace_functions[k.value], v.value))
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700290
291 if exiting:
292 print("Detaching...")
293 exit()
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700294 else:
295 self.probe.clear()
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700296
297if __name__ == "__main__":
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800298 try:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700299 Tool().run()
300 except Exception:
301 if debug:
302 traceback.print_exc()
303 elif sys.exc_info()[0] is not SystemExit:
304 print(sys.exc_info()[1])