blob: ff64df910dabca83ccb27b8ae55811302b1bc9b3 [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#
7# USAGE: funccount [-h] [-p PID] [-i INTERVAL] [-T] [-r] pattern
8#
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
221 ./funccount -p 185 'vfs_*' # count vfs calls for PID 181 only
222 ./funccount t:sched:sched_fork # count calls to the sched_fork tracepoint
Brendan Greggb6035b62017-01-10 17:36:07 -0800223 ./funccount -p 185 u:node:gc* # count all GC USDT probes in node, PID 185
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700224 ./funccount c:malloc # count all malloc() calls in libc
Brendan Greggb6035b62017-01-10 17:36:07 -0800225 ./funccount go:os.* # count all "os.*" calls in libgo
226 ./funccount -p 185 go:os.* # count all "os.*" calls in libgo, PID 185
227 ./funccount ./test:read* # count "read*" calls in the ./test binary
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700228 """
229 parser = argparse.ArgumentParser(
230 description="Count functions, tracepoints, and USDT probes",
231 formatter_class=argparse.RawDescriptionHelpFormatter,
232 epilog=examples)
233 parser.add_argument("-p", "--pid", type=int,
234 help="trace this PID only")
235 parser.add_argument("-i", "--interval", default=99999999,
236 help="summary interval, seconds")
237 parser.add_argument("-T", "--timestamp", action="store_true",
238 help="include timestamp on output")
239 parser.add_argument("-r", "--regexp", action="store_true",
240 help="use regular expressions. Default is \"*\" wildcards only.")
241 parser.add_argument("-d", "--debug", action="store_true",
242 help="print BPF program before starting (for debugging purposes)")
243 parser.add_argument("pattern",
244 help="search expression for events")
245 self.args = parser.parse_args()
246 global debug
247 debug = self.args.debug
248 self.probe = Probe(self.args.pattern, self.args.regexp, self.args.pid)
249
250 @staticmethod
251 def _signal_ignore(signal, frame):
252 print()
253
254 def run(self):
255 self.probe.load()
256 self.probe.attach()
257 print("Tracing %d functions for \"%s\"... Hit Ctrl-C to end." %
258 (self.probe.matched, self.args.pattern))
259 exiting = 0 if self.args.interval else 1
260 while True:
261 try:
262 sleep(int(self.args.interval))
263 except KeyboardInterrupt:
264 exiting = 1
265 # as cleanup can take many seconds, trap Ctrl-C:
266 signal.signal(signal.SIGINT, Tool._signal_ignore)
267
268 print()
269 if self.args.timestamp:
270 print("%-8s\n" % strftime("%H:%M:%S"), end="")
271
272 print("%-36s %8s" % ("FUNC", "COUNT"))
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700273 counts = self.probe.counts()
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700274 for k, v in sorted(counts.items(),
275 key=lambda counts: counts[1].value):
276 if v.value == 0:
277 continue
278 print("%-36s %8d" %
279 (self.probe.trace_functions[k.value], v.value))
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700280
281 if exiting:
282 print("Detaching...")
283 exit()
Sasha Goldshteindd7ec5a2016-10-25 07:18:24 -0700284 else:
285 self.probe.clear()
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700286
287if __name__ == "__main__":
Alexei Starovoitovbdf07732016-01-14 10:09:20 -0800288 try:
Sasha Goldshteinff3b9f32016-10-08 07:01:21 -0700289 Tool().run()
290 except Exception:
291 if debug:
292 traceback.print_exc()
293 elif sys.exc_info()[0] is not SystemExit:
294 print(sys.exc_info()[1])