blob: 5554014fc7147b780c1f802e162dc258e3984161 [file] [log] [blame]
Sasha Goldshtein07175d02016-10-06 01:11:55 +03001#!/usr/bin/env python
Brendan Gregg38cef482016-01-15 17:26:30 -08002#
Sasha Goldshtein07175d02016-10-06 01:11:55 +03003# stackcount Count events and their stack traces.
Brendan Gregg38cef482016-01-15 17:26:30 -08004# For Linux, uses BCC, eBPF.
5#
Brendan Gregg58889892017-09-03 12:08:52 -07006# USAGE: stackcount.py [-h] [-p PID] [-i INTERVAL] [-D DURATION] [-T] [-r] [-s]
7# [-P] [-K] [-U] [-v] [-d] [-f] [--debug]
Brendan Gregg38cef482016-01-15 17:26:30 -08008#
9# 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.
11#
Brendan Gregg38cef482016-01-15 17:26:30 -080012# Copyright 2016 Netflix, Inc.
13# Licensed under the Apache License, Version 2.0 (the "License")
14#
Sasha Goldshtein07175d02016-10-06 01:11:55 +030015# 12-Jan-2016 Brendan Gregg Created this.
16# 09-Jul-2016 Sasha Goldshtein Generalized for uprobes and tracepoints.
Brendan Gregg38cef482016-01-15 17:26:30 -080017
18from __future__ import print_function
Sasha Goldshtein07175d02016-10-06 01:11:55 +030019from bcc import BPF, USDT
Brendan Gregg38cef482016-01-15 17:26:30 -080020from time import sleep, strftime
21import argparse
Sasha Goldshtein07175d02016-10-06 01:11:55 +030022import re
Brendan Gregg38cef482016-01-15 17:26:30 -080023import signal
Sasha Goldshtein07175d02016-10-06 01:11:55 +030024import sys
25import traceback
Brendan Gregg38cef482016-01-15 17:26:30 -080026
Sasha Goldshtein07175d02016-10-06 01:11:55 +030027debug = False
Brendan Gregg38cef482016-01-15 17:26:30 -080028
Sasha Goldshtein07175d02016-10-06 01:11:55 +030029class Probe(object):
Brendan Gregg58889892017-09-03 12:08:52 -070030 def __init__(self, pattern, kernel_stack, user_stack, use_regex=False,
31 pid=None, per_pid=False):
Sasha Goldshtein07175d02016-10-06 01:11:55 +030032 """Init a new probe.
Brendan Gregg38cef482016-01-15 17:26:30 -080033
Sasha Goldshtein07175d02016-10-06 01:11:55 +030034 Init the probe from the pattern provided by the user. The supported
35 patterns mimic the 'trace' and 'argdist' tools, but are simpler because
36 we don't have to distinguish between probes and retprobes.
Brendan Gregg38cef482016-01-15 17:26:30 -080037
Sasha Goldshtein07175d02016-10-06 01:11:55 +030038 func -- probe a kernel function
39 lib:func -- probe a user-space function in the library 'lib'
40 p::func -- same thing as 'func'
41 p:lib:func -- same thing as 'lib:func'
42 t:cat:event -- probe a kernel tracepoint
43 u:lib:probe -- probe a USDT tracepoint
44 """
Brendan Gregg58889892017-09-03 12:08:52 -070045 self.kernel_stack = kernel_stack
46 self.user_stack = user_stack
Sasha Goldshtein07175d02016-10-06 01:11:55 +030047 parts = pattern.split(':')
48 if len(parts) == 1:
49 parts = ["p", "", parts[0]]
50 elif len(parts) == 2:
51 parts = ["p", parts[0], parts[1]]
52 elif len(parts) == 3:
53 if parts[0] == "t":
54 parts = ["t", "", "%s:%s" % tuple(parts[1:])]
55 if parts[0] not in ["p", "t", "u"]:
56 raise Exception("Type must be 'p', 't', or 'u', but got %s" %
57 parts[0])
58 else:
59 raise Exception("Too many ':'-separated components in pattern %s" %
60 pattern)
Brendan Gregg38cef482016-01-15 17:26:30 -080061
Sasha Goldshtein07175d02016-10-06 01:11:55 +030062 (self.type, self.library, self.pattern) = parts
63 if not use_regex:
64 self.pattern = self.pattern.replace('*', '.*')
65 self.pattern = '^' + self.pattern + '$'
66
67 if (self.type == "p" and self.library) or self.type == "u":
68 libpath = BPF.find_library(self.library)
69 if libpath is None:
70 # This might be an executable (e.g. 'bash')
71 libpath = BPF.find_exe(self.library)
72 if libpath is None or len(libpath) == 0:
73 raise Exception("unable to find library %s" % self.library)
74 self.library = libpath
75
76 self.pid = pid
77 self.per_pid = per_pid
78 self.matched = 0
79
80 def is_kernel_probe(self):
81 return self.type == "t" or (self.type == "p" and self.library == "")
82
83 def attach(self):
84 if self.type == "p":
85 if self.library:
86 self.bpf.attach_uprobe(name=self.library,
87 sym_re=self.pattern,
88 fn_name="trace_count",
89 pid=self.pid or -1)
90 self.matched = self.bpf.num_open_uprobes()
91 else:
92 self.bpf.attach_kprobe(event_re=self.pattern,
Teng Qinfd244052017-12-15 15:53:53 -080093 fn_name="trace_count")
Sasha Goldshtein07175d02016-10-06 01:11:55 +030094 self.matched = self.bpf.num_open_kprobes()
95 elif self.type == "t":
96 self.bpf.attach_tracepoint(tp_re=self.pattern,
Teng Qinfd244052017-12-15 15:53:53 -080097 fn_name="trace_count")
Sasha Goldshtein07175d02016-10-06 01:11:55 +030098 self.matched = self.bpf.num_open_tracepoints()
99 elif self.type == "u":
Sasha Goldshteinb778ccd2016-10-08 06:59:37 -0700100 pass # Nothing to do -- attach already happened in `load`
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300101
102 if self.matched == 0:
Sasha Goldshteinb778ccd2016-10-08 06:59:37 -0700103 raise Exception("No functions matched by pattern %s" %
104 self.pattern)
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300105
106 def load(self):
Brendan Gregg58889892017-09-03 12:08:52 -0700107 ctx_name = "ctx"
108 stack_trace = ""
109 if self.user_stack:
110 stack_trace += """
111 key.user_stack_id = stack_traces.get_stackid(
112 %s, BPF_F_REUSE_STACKID | BPF_F_USER_STACK
113 );""" % (ctx_name)
114 else:
115 stack_trace += "key.user_stack_id = -1;"
116 if self.kernel_stack:
117 stack_trace += """
118 key.kernel_stack_id = stack_traces.get_stackid(
119 %s, BPF_F_REUSE_STACKID
120 );""" % (ctx_name)
121 else:
122 stack_trace += "key.kernel_stack_id = -1;"
123
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300124 trace_count_text = """
125int trace_count(void *ctx) {
Brendan Gregg38cef482016-01-15 17:26:30 -0800126 FILTER
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300127 struct key_t key = {};
Brendan Gregg58889892017-09-03 12:08:52 -0700128 key.tgid = GET_TGID;
129 STORE_COMM
130 %s
Javier Honduvilla Coto64bf9652018-08-01 06:50:19 +0200131 counts.increment(key);
Brendan Gregg38cef482016-01-15 17:26:30 -0800132 return 0;
133}
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300134 """
Brendan Gregg58889892017-09-03 12:08:52 -0700135 trace_count_text = trace_count_text % (stack_trace)
136
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300137 bpf_text = """#include <uapi/linux/ptrace.h>
Brendan Gregg58889892017-09-03 12:08:52 -0700138#include <linux/sched.h>
Brendan Gregg38cef482016-01-15 17:26:30 -0800139
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300140struct key_t {
Brendan Gregg58889892017-09-03 12:08:52 -0700141 // no pid (thread ID) so that we do not needlessly split this key
142 u32 tgid;
143 int kernel_stack_id;
144 int user_stack_id;
145 char name[TASK_COMM_LEN];
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300146};
Brendan Gregg38cef482016-01-15 17:26:30 -0800147
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300148BPF_HASH(counts, struct key_t);
149BPF_STACK_TRACE(stack_traces, 1024);
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300150 """
151
152 # We really mean the tgid from the kernel's perspective, which is in
153 # the top 32 bits of bpf_get_current_pid_tgid().
154 if self.is_kernel_probe() and self.pid:
155 trace_count_text = trace_count_text.replace('FILTER',
156 ('u32 pid; pid = bpf_get_current_pid_tgid() >> 32; ' +
157 'if (pid != %d) { return 0; }') % (self.pid))
158 else:
159 trace_count_text = trace_count_text.replace('FILTER', '')
160
161 # We need per-pid statistics when tracing a user-space process, because
162 # the meaning of the symbols depends on the pid. We also need them if
Brendan Gregg58889892017-09-03 12:08:52 -0700163 # per-pid statistics were requested with -P, or for user stacks.
164 if self.per_pid or not self.is_kernel_probe() or self.user_stack:
165 trace_count_text = trace_count_text.replace('GET_TGID',
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300166 'bpf_get_current_pid_tgid() >> 32')
Brendan Gregg58889892017-09-03 12:08:52 -0700167 trace_count_text = trace_count_text.replace('STORE_COMM',
168 'bpf_get_current_comm(&key.name, sizeof(key.name));')
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300169 else:
Brendan Gregg58889892017-09-03 12:08:52 -0700170 # kernel stacks only. skip splitting on PID so these aggregate
171 # together, and don't store the process name.
Sasha Goldshteinb778ccd2016-10-08 06:59:37 -0700172 trace_count_text = trace_count_text.replace(
Brendan Gregg58889892017-09-03 12:08:52 -0700173 'GET_TGID', '0xffffffff')
174 trace_count_text = trace_count_text.replace('STORE_COMM', '')
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300175
176 self.usdt = None
177 if self.type == "u":
178 self.usdt = USDT(path=self.library, pid=self.pid)
179 for probe in self.usdt.enumerate_probes():
180 if not self.pid and (probe.bin_path != self.library):
181 continue
182 if re.match(self.pattern, probe.name):
183 # This hack is required because the bpf_usdt_readarg
184 # functions generated need different function names for
185 # each attached probe. If we just stick to trace_count,
186 # we'd get multiple bpf_usdt_readarg helpers with the same
187 # name when enabling more than one USDT probe.
188 new_func = "trace_count_%d" % self.matched
189 bpf_text += trace_count_text.replace(
190 "trace_count", new_func)
191 self.usdt.enable_probe(probe.name, new_func)
192 self.matched += 1
193 if debug:
194 print(self.usdt.get_text())
195 else:
196 bpf_text += trace_count_text
197
198 if debug:
199 print(bpf_text)
Sasha Goldshteinb778ccd2016-10-08 06:59:37 -0700200 self.bpf = BPF(text=bpf_text,
201 usdt_contexts=[self.usdt] if self.usdt else [])
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300202
203class Tool(object):
204 def __init__(self):
205 examples = """examples:
Sasha Goldshteinb778ccd2016-10-08 06:59:37 -0700206 ./stackcount submit_bio # count kernel stack traces for submit_bio
Brendan Gregg58889892017-09-03 12:08:52 -0700207 ./stackcount -d ip_output # include a user/kernel stack delimiter
Sasha Goldshteinb778ccd2016-10-08 06:59:37 -0700208 ./stackcount -s ip_output # show symbol offsets
209 ./stackcount -sv ip_output # show offsets and raw addresses (verbose)
210 ./stackcount 'tcp_send*' # count stacks for funcs matching tcp_send*
211 ./stackcount -r '^tcp_send.*' # same as above, using regular expressions
212 ./stackcount -Ti 5 ip_output # output every 5 seconds, with timestamps
213 ./stackcount -p 185 ip_output # count ip_output stacks for PID 185 only
214 ./stackcount -p 185 c:malloc # count stacks for malloc in PID 185
215 ./stackcount t:sched:sched_fork # count stacks for sched_fork tracepoint
216 ./stackcount -p 185 u:node:* # count stacks for all USDT probes in node
Brendan Gregg58889892017-09-03 12:08:52 -0700217 ./stackcount -K t:sched:sched_switch # kernel stacks only
218 ./stackcount -U t:sched:sched_switch # user stacks only
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300219 """
220 parser = argparse.ArgumentParser(
221 description="Count events and their stack traces",
222 formatter_class=argparse.RawDescriptionHelpFormatter,
223 epilog=examples)
224 parser.add_argument("-p", "--pid", type=int,
225 help="trace this PID only")
Brendan Gregg58889892017-09-03 12:08:52 -0700226 parser.add_argument("-i", "--interval",
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300227 help="summary interval, seconds")
Brendan Gregg58889892017-09-03 12:08:52 -0700228 parser.add_argument("-D", "--duration",
229 help="total duration of trace, seconds")
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300230 parser.add_argument("-T", "--timestamp", action="store_true",
231 help="include timestamp on output")
232 parser.add_argument("-r", "--regexp", action="store_true",
233 help="use regular expressions. Default is \"*\" wildcards only.")
234 parser.add_argument("-s", "--offset", action="store_true",
235 help="show address offsets")
236 parser.add_argument("-P", "--perpid", action="store_true",
237 help="display stacks separately for each process")
Brendan Gregg58889892017-09-03 12:08:52 -0700238 parser.add_argument("-K", "--kernel-stacks-only",
239 action="store_true", help="kernel stack only", default=False)
240 parser.add_argument("-U", "--user-stacks-only",
241 action="store_true", help="user stack only", default=False)
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300242 parser.add_argument("-v", "--verbose", action="store_true",
243 help="show raw addresses")
Brendan Gregg58889892017-09-03 12:08:52 -0700244 parser.add_argument("-d", "--delimited", action="store_true",
245 help="insert delimiter between kernel/user stacks")
246 parser.add_argument("-f", "--folded", action="store_true",
247 help="output folded format")
248 parser.add_argument("--debug", action="store_true",
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300249 help="print BPF program before starting (for debugging purposes)")
250 parser.add_argument("pattern",
251 help="search expression for events")
252 self.args = parser.parse_args()
253 global debug
254 debug = self.args.debug
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300255
Brendan Gregg58889892017-09-03 12:08:52 -0700256 if self.args.duration and not self.args.interval:
257 self.args.interval = self.args.duration
258 if not self.args.interval:
259 self.args.interval = 99999999
260
261 if self.args.kernel_stacks_only and self.args.user_stacks_only:
262 print("ERROR: -K and -U are mutually exclusive. If you want " +
263 "both stacks, that is the default.")
264 exit()
265 if not self.args.kernel_stacks_only and not self.args.user_stacks_only:
266 self.kernel_stack = True
267 self.user_stack = True
268 else:
269 self.kernel_stack = self.args.kernel_stacks_only
270 self.user_stack = self.args.user_stacks_only
271
272 self.probe = Probe(self.args.pattern,
273 self.kernel_stack, self.user_stack,
274 self.args.regexp, self.args.pid, self.args.perpid)
275 self.need_delimiter = self.args.delimited and not (
276 self.args.kernel_stacks_only or self.args.user_stacks_only)
277
278 def _print_kframe(self, addr):
279 print(" ", end="")
280 if self.args.verbose:
281 print("%-16x " % addr, end="")
282 if self.args.offset:
283 print("%s" % self.probe.bpf.ksym(addr, show_offset=True))
284 else:
285 print("%s" % self.probe.bpf.ksym(addr))
286
287 def _print_uframe(self, addr, pid):
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300288 print(" ", end="")
289 if self.args.verbose:
290 print("%-16x " % addr, end="")
291 if self.args.offset:
Sasha Goldshtein01553852017-02-09 03:58:09 -0500292 print("%s" % self.probe.bpf.sym(addr, pid, show_offset=True))
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300293 else:
294 print("%s" % self.probe.bpf.sym(addr, pid))
295
296 @staticmethod
297 def _signal_ignore(signal, frame):
298 print()
299
Brendan Gregg58889892017-09-03 12:08:52 -0700300 def _print_comm(self, comm, pid):
301 print(" %s [%d]" % (comm, pid))
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300302
303 def run(self):
304 self.probe.load()
305 self.probe.attach()
Brendan Gregg58889892017-09-03 12:08:52 -0700306 if not self.args.folded:
307 print("Tracing %d functions for \"%s\"... Hit Ctrl-C to end." %
308 (self.probe.matched, self.args.pattern))
309 b = self.probe.bpf
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300310 exiting = 0 if self.args.interval else 1
Brendan Gregg58889892017-09-03 12:08:52 -0700311 seconds = 0
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300312 while True:
313 try:
314 sleep(int(self.args.interval))
Brendan Gregg58889892017-09-03 12:08:52 -0700315 seconds += int(self.args.interval)
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300316 except KeyboardInterrupt:
317 exiting = 1
318 # as cleanup can take many seconds, trap Ctrl-C:
319 signal.signal(signal.SIGINT, Tool._signal_ignore)
Brendan Gregg58889892017-09-03 12:08:52 -0700320 if self.args.duration and seconds >= int(self.args.duration):
321 exiting = 1
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300322
Brendan Gregg58889892017-09-03 12:08:52 -0700323 if not self.args.folded:
324 print()
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300325 if self.args.timestamp:
326 print("%-8s\n" % strftime("%H:%M:%S"), end="")
327
328 counts = self.probe.bpf["counts"]
329 stack_traces = self.probe.bpf["stack_traces"]
330 self.comm_cache = {}
331 for k, v in sorted(counts.items(),
332 key=lambda counts: counts[1].value):
Brendan Gregg58889892017-09-03 12:08:52 -0700333 user_stack = [] if k.user_stack_id < 0 else \
334 stack_traces.walk(k.user_stack_id)
335 kernel_stack = [] if k.kernel_stack_id < 0 else \
336 stack_traces.walk(k.kernel_stack_id)
337
338 if self.args.folded:
339 # print folded stack output
340 user_stack = list(user_stack)
341 kernel_stack = list(kernel_stack)
jeromemarchandb96ebcd2018-10-10 01:58:15 +0200342 line = [k.name.decode('utf-8', 'replace')] + \
Brendan Gregg58889892017-09-03 12:08:52 -0700343 [b.sym(addr, k.tgid) for addr in
344 reversed(user_stack)] + \
345 (self.need_delimiter and ["-"] or []) + \
346 [b.ksym(addr) for addr in reversed(kernel_stack)]
347 print("%s %d" % (";".join(line), v.value))
348 else:
349 # print multi-line stack output
350 for addr in kernel_stack:
351 self._print_kframe(addr)
352 if self.need_delimiter:
353 print(" --")
354 for addr in user_stack:
355 self._print_uframe(addr, k.tgid)
356 if not self.args.pid and k.tgid != 0xffffffff:
357 self._print_comm(k.name, k.tgid)
358 print(" %d\n" % v.value)
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300359 counts.clear()
360
361 if exiting:
Brendan Gregg58889892017-09-03 12:08:52 -0700362 if not self.args.folded:
363 print("Detaching...")
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300364 exit()
365
366if __name__ == "__main__":
Brendan Gregg38cef482016-01-15 17:26:30 -0800367 try:
Sasha Goldshtein07175d02016-10-06 01:11:55 +0300368 Tool().run()
369 except Exception:
370 if debug:
371 traceback.print_exc()
372 elif sys.exc_info()[0] is not SystemExit:
373 print(sys.exc_info()[1])