blob: b6cc3b68ffa4b174996b37a7b3d17cf2f8832d2d [file] [log] [blame]
Brendan Gregg38cef482016-01-15 17:26:30 -08001#!/usr/bin/python
2#
3# stackcount Count kernel function calls and their stack traces.
4# For Linux, uses BCC, eBPF.
5#
6# USAGE: stackcount [-h] [-p PID] [-i INTERVAL] [-T] [-r] pattern
7#
8# The pattern is a string with optional '*' wildcards, similar to file
9# globbing. If you'd prefer to use regular expressions, use the -r option.
10#
11# The current implementation uses an unrolled loop for x86_64, and was written
12# as a proof of concept. This implementation should be replaced in the future
13# with an appropriate bpf_ call, when available.
14#
15# Currently limited to a stack trace depth of 11 (maxdepth + 1).
16#
17# Copyright 2016 Netflix, Inc.
18# Licensed under the Apache License, Version 2.0 (the "License")
19#
20# 12-Jan-2016 Brendan Gregg Created this.
21
22from __future__ import print_function
23from bcc import BPF
24from time import sleep, strftime
25import argparse
26import signal
27
28# arguments
29examples = """examples:
30 ./stackcount submit_bio # count kernel stack traces for submit_bio
31 ./stackcount ip_output # count kernel stack traces for ip_output
32 ./stackcount -s ip_output # show symbol offsets
33 ./stackcount -sv ip_output # show offsets and raw addresses (verbose)
34 ./stackcount 'tcp_send*' # count stacks for funcs matching tcp_send*
35 ./stackcount -r '^tcp_send.*' # same as above, using regular expressions
36 ./stackcount -Ti 5 ip_output # output every 5 seconds, with timestamps
37 ./stackcount -p 185 ip_output # count ip_output stacks for PID 185 only
38"""
39parser = argparse.ArgumentParser(
40 description="Count kernel function calls and their stack traces",
41 formatter_class=argparse.RawDescriptionHelpFormatter,
42 epilog=examples)
43parser.add_argument("-p", "--pid",
44 help="trace this PID only")
45parser.add_argument("-i", "--interval", default=99999999,
46 help="summary interval, seconds")
47parser.add_argument("-T", "--timestamp", action="store_true",
48 help="include timestamp on output")
49parser.add_argument("-r", "--regexp", action="store_true",
50 help="use regular expressions. Default is \"*\" wildcards only.")
51parser.add_argument("-s", "--offset", action="store_true",
52 help="show address offsets")
53parser.add_argument("-v", "--verbose", action="store_true",
54 help="show raw addresses")
55parser.add_argument("pattern",
56 help="search expression for kernel functions")
57args = parser.parse_args()
58pattern = args.pattern
59if not args.regexp:
60 pattern = pattern.replace('*', '.*')
61 pattern = '^' + pattern + '$'
62offset = args.offset
63verbose = args.verbose
64debug = 0
65maxdepth = 10 # and MAXDEPTH
66
67# signal handler
68def signal_ignore(signal, frame):
69 print()
70
71# load BPF program
72bpf_text = """
73#include <uapi/linux/ptrace.h>
74
75#define MAXDEPTH 10
76
77struct key_t {
78 u64 ip;
79 u64 ret[MAXDEPTH];
80};
81BPF_HASH(counts, struct key_t);
82
83static u64 get_frame(u64 *bp) {
84 if (*bp) {
85 // The following stack walker is x86_64 specific
86 u64 ret = 0;
87 if (bpf_probe_read(&ret, sizeof(ret), (void *)(*bp+8)))
88 return 0;
89 if (!ret || ret < __START_KERNEL_map)
90 return 0;
91 if (bpf_probe_read(bp, sizeof(*bp), (void *)*bp))
92 bp = 0;
93 return ret;
94 }
95 return 0;
96}
97
98int trace_count(struct pt_regs *ctx) {
99 FILTER
100 struct key_t key = {};
101 u64 zero = 0, *val, bp = 0;
102 int depth = 0;
103
104 key.ip = ctx->ip;
105 bp = ctx->bp;
106
107 // unrolled loop, 10 (MAXDEPTH) frames deep:
108 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
109 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
110 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
111 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
112 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
113 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
114 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
115 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
116 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
117 if (!(key.ret[depth++] = get_frame(&bp))) goto out;
118
119out:
120 val = counts.lookup_or_init(&key, &zero);
121 (*val)++;
122 return 0;
123}
124"""
125if args.pid:
126 bpf_text = bpf_text.replace('FILTER',
127 ('u32 pid; pid = bpf_get_current_pid_tgid(); ' +
128 'if (pid != %s) { return 0; }') % (args.pid))
129else:
130 bpf_text = bpf_text.replace('FILTER', '')
131if debug:
132 print(bpf_text)
133b = BPF(text=bpf_text)
134b.attach_kprobe(event_re=pattern, fn_name="trace_count")
135matched = b.num_open_kprobes()
136if matched == 0:
137 print("0 functions matched by \"%s\". Exiting." % args.pattern)
138 exit()
139
140# header
141print("Tracing %d functions for \"%s\"... Hit Ctrl-C to end." %
142 (matched, args.pattern))
143
144def print_frame(addr):
145 print(" ", end="")
146 if verbose:
147 print("%-16x " % addr, end="")
148 if offset:
149 print("%s" % b.ksymaddr(addr))
150 else:
151 print("%s" % b.ksym(addr))
152
153# output
154exiting = 0 if args.interval else 1
155while (1):
156 try:
157 sleep(int(args.interval))
158 except KeyboardInterrupt:
159 exiting = 1
160 # as cleanup can take many seconds, trap Ctrl-C:
161 signal.signal(signal.SIGINT, signal_ignore)
162
163 print()
164 if args.timestamp:
165 print("%-8s\n" % strftime("%H:%M:%S"), end="")
166
167 counts = b.get_table("counts")
168 for k, v in sorted(counts.items(), key=lambda counts: counts[1].value):
169 print_frame(k.ip)
170 for i in range(0, maxdepth):
171 if k.ret[i] == 0:
172 break
173 print_frame(k.ret[i])
174 print(" %d\n" % v.value)
175 counts.clear()
176
177 if exiting:
178 print("Detaching...")
179 exit()