语法
stackcount语法与funccount相似都是由选项和eventname构成
stackcount [options] eventname
eventname的语法是:
- name或p:name:对内核函数name()进行插装。
- lib:name或p:lib:name:对用户态lib库中的函数name进行插桩。
- path:name:对于path路径下文件的用户态函数name进行插桩。
- t:system:name:对名为system:name的内核跟踪点进行插装。
- u:lib:name:对lib库中名为name的USDT探针进行插桩。
- *:用来匹配任意字符的通配符。
选项:
- -h、–help:显示帮助信息
- -p PID、–pid PID:只对该PID进行跟踪
- -i INTERVAL、–interval INTERVAL:统计总数,每
INTERVAL
秒 - -d DURATION、–duration DURATION:总共跟踪的秒数
- -T、–timestamp:输出时间
- -r、–regexp:使用正则表达式
- -s、–offset:显示地址偏移
- -P、–perpid:对每个进程分别显示栈信息
- -K、–kernel-stacks-only:仅显示内核堆栈信息
- -U、–user-stacks-only:仅显示用户态的堆栈信息
- -v、–verbose:显示行地址
- -d、–delimited:插入分隔符在内核栈和用户栈间
- -f、–folded:输出折叠格式
- –debug:启动前打印BPF程序信息
示例
对创建块I/O的函数调用栈进行计数:
stackcount-bpfcc t:block:block_rq_insert
对发送IP数据包的调用栈进行计数
stackcount-bpfcc ip_output
对发送IP数据包的调用栈进行计数,同时显示对应的PID:
stackcount-bpfcc -P ip_output
对导致线程阻塞并且导致脱离CPU的调用栈进行计数:
stackcount-bpfcc t:sched:sched_switch
对导致系统调用read的调用栈进行计数:
stackcount-bpfcc t:syscalls:sys_enter_read
源码
from __future__ import print_function
from bcc import BPF, USDT
from time import sleep, strftime
import argparse
import re
import signal
import sys
import traceback
debug = False
class Probe(object):
def \_\_init\_\_(self, pattern, kernel_stack, user_stack, use_regex=False,
pid=None, per_pid=False, cpu=None):
"""Init a new probe.
Init the probe from the pattern provided by the user. The supported
patterns mimic the 'trace' and 'argdist' tools, but are simpler because
we don't have to distinguish between probes and retprobes.
func -- probe a kernel function
lib:func -- probe a user-space function in the library 'lib'
p::func -- same thing as 'func'
p:lib:func -- same thing as 'lib:func'
t:cat:event -- probe a kernel tracepoint
u:lib:probe -- probe a USDT tracepoint
"""
self.kernel_stack = kernel_stack
self.user_stack = user_stack
parts = pattern.split(':')
if len(parts) == 1:
parts = ["p", "", parts[0]]
elif len(parts) == 2:
parts = ["p", parts[0], parts[1]]
elif len(parts) == 3:
if parts[0] == "t":
parts = ["t", "", "%s:%s" % tuple(parts[1:])]
if parts[0] not in ["p", "t", "u"]:
raise Exception("Type must be 'p', 't', or 'u', but got %s" %
parts[0])
else:
raise Exception("Too many ':'-separated components in pattern %s" %
pattern)
(self.type, self.library, self.pattern) = parts
if not use_regex:
self.pattern = self.pattern.replace('\*', '.\*')
self.pattern = '^' + self.pattern + '$'
if (self.type == "p" and self.library) or self.type == "u":
libpath = BPF.find_library(self.library)
if libpath is None:
# This might be an executable (e.g. 'bash')
libpath = BPF.find_exe(self.library)
if libpath is None or len(libpath) == 0:
raise Exception("unable to find library %s" % self.library)
self.library = libpath
self.pid = pid
self.per_pid = per_pid
self.cpu = cpu
self.matched = 0
def is\_kernel\_probe(self):
return self.type == "t" or (self.type == "p" and self.library == "")
def attach(self):
if self.type == "p":
if self.library:
self.bpf.attach_uprobe(name=self.library,
sym_re=self.pattern,
fn_name="trace\_count",
pid=self.pid or -1)
self.matched = self.bpf.num_open_uprobes()
else:
self.bpf.attach_kprobe(event_re=self.pattern,
fn_name="trace\_count")
self.matched = self.bpf.num_open_kprobes()
elif self.type == "t":
self.bpf.attach_tracepoint(tp_re=self.pattern,
fn_name="trace\_count")
self.matched = self.bpf.num_open_tracepoints()
elif self.type == "u":
pass # Nothing to do -- attach already happened in `load`
if self.matched == 0:
raise Exception("No functions matched by pattern %s" %
self.pattern)
def load(self):
ctx_name = "ctx"
stack_trace = ""
if self.user_stack:
stack_trace += """
key.user\_stack\_id = stack\_traces.get\_stackid(
%s, BPF\_F\_USER\_STACK
);""" % (ctx_name)
else:
stack_trace += "key.user\_stack\_id = -1;"
if self.kernel_stack:
stack_trace += """
key.kernel\_stack\_id = stack\_traces.get\_stackid(
%s, 0
);""" % (ctx_name)
else:
stack_trace += "key.kernel\_stack\_id = -1;"
trace_count_text = """
int trace\_count(void \*ctx) {
FILTER
struct key\_t key = {};
key.tgid = GET\_TGID;
STORE\_COMM
%s
counts.atomic\_increment(key);
return 0;
}
"""
trace_count_text = trace_count_text % (stack_trace)
bpf_text = """#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
struct key\_t {
// no pid (thread ID) so that we do not needlessly split this key
u32 tgid;
int kernel\_stack\_id;
int user\_stack\_id;
char name[TASK\_COMM\_LEN];
};
BPF\_HASH(counts, struct key\_t);
BPF\_STACK\_TRACE(stack\_traces, 1024);
"""
filter_text = []
# We really mean the tgid from the kernel's perspective, which is in
# the top 32 bits of bpf\_get\_current\_pid\_tgid().
if self.is_kernel_probe() and self.pid:
filter_text.append('u32 pid; pid = bpf\_get\_current\_pid\_tgid() >> 32; ' +
'if (pid != %d) { return 0; }' % self.pid)
if self.is_kernel_probe() and self.cpu:
filter_text.append('struct task\_struct \*task; task = (struct task\_struct\*)bpf\_get\_current\_task(); ' +
'if (task->cpu != %d) { return 0; }' % self.cpu)
trace_count_text = trace_count_text.replace('FILTER', '\n '.join(filter_text))
# Do per-pid statistics iff -P is provided
if self.per_pid:
trace_count_text = trace_count_text.replace('GET\_TGID',
'bpf\_get\_current\_pid\_tgid() >> 32')
trace_count_text = trace_count_text.replace('STORE\_COMM',
'bpf\_get\_current\_comm(&key.name, sizeof(key.name));')
else:
# skip splitting on PID so these aggregate
# together, and don't store the process name.
trace_count_text = trace_count_text.replace(
'GET\_TGID', '0xffffffff')
trace_count_text = trace_count_text.replace('STORE\_COMM', '')
self.usdt = None
if self.type == "u":
self.usdt = USDT(path=self.library, pid=self.pid)
for probe in self.usdt.enumerate_probes():
if not self.pid and (probe.bin_path != self.library):
continue
if re.match(self.pattern, probe.name):
# This hack is required because the bpf\_usdt\_readarg
# functions generated need different function names for
# each attached probe. If we just stick to trace\_count,
# we'd get multiple bpf\_usdt\_readarg helpers with the same
# name when enabling more than one USDT probe.
new_func = "trace\_count\_%d" % self.matched
bpf_text += trace_count_text.replace(
"trace\_count", new_func)
self.usdt.enable_probe(probe.name, new_func)
self.matched += 1
if debug:
print(self.usdt.get_text())
else:
bpf_text += trace_count_text
if debug:
print(bpf_text)
self.bpf = BPF(text=bpf_text,
usdt_contexts=[self.usdt] if self.usdt else [])
class Tool(object):
def \_\_init\_\_(self):
examples = """examples:
./stackcount submit\_bio # count kernel stack traces for submit\_bio
./stackcount -d ip\_output # include a user/kernel stack delimiter
./stackcount -s ip\_output # show symbol offsets
./stackcount -sv ip\_output # show offsets and raw addresses (verbose)
./stackcount 'tcp\_send\*' # count stacks for funcs matching tcp\_send\*
./stackcount -r '^tcp\_send.\*' # same as above, using regular expressions
./stackcount -Ti 5 ip\_output # output every 5 seconds, with timestamps
./stackcount -p 185 ip\_output # count ip\_output stacks for PID 185 only
./stackcount -c 1 put\_prev\_entity # count put\_prev\_entity stacks for CPU 1 only
./stackcount -p 185 c:malloc # count stacks for malloc in PID 185
./stackcount t:sched:sched\_fork # count stacks for sched\_fork tracepoint
./stackcount -p 185 u:node:\* # count stacks for all USDT probes in node
./stackcount -K t:sched:sched\_switch # kernel stacks only
./stackcount -U t:sched:sched\_switch # user stacks only
"""
parser = argparse.ArgumentParser(
description="Count events and their stack traces",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=examples)
parser.add_argument("-p", "--pid", type=int,
help="trace this PID only")
parser.add_argument("-c", "--cpu", type=int,
help="trace this CPU only")
parser.add_argument("-i", "--interval",
help="summary interval, seconds")
parser.add_argument("-D", "--duration",
help="total duration of trace, seconds")
parser.add_argument("-T", "--timestamp", action="store\_true",
help="include timestamp on output")
parser.add_argument("-r", "--regexp", action="store\_true",
help="use regular expressions. Default is \"\*\" wildcards only.")
parser.add_argument("-s", "--offset", action="store\_true",
help="show address offsets")
parser.add_argument("-P", "--perpid", action="store\_true",
help="display stacks separately for each process")
parser.add_argument("-K", "--kernel-stacks-only",
action="store\_true", help="kernel stack only", default=False)
parser.add_argument("-U", "--user-stacks-only",
action="store\_true", help="user stack only", default=False)
parser.add_argument("-v", "--verbose", action="store\_true",
help="show raw addresses")
parser.add_argument("-d", "--delimited", action="store\_true",
help="insert delimiter between kernel/user stacks")
parser.add_argument("-f", "--folded", action="store\_true",
help="output folded format")
parser.add_argument("--debug", action="store\_true",
help="print BPF program before starting (for debugging purposes)")
parser.add_argument("pattern",
help="search expression for events")
self.args = parser.parse_args()
global debug
debug = self.args.debug
if self.args.duration and not self.args.interval:
self.args.interval = self.args.duration
if not self.args.interval:
self.args.interval = 99999999
if self.args.kernel_stacks_only and self.args.user_stacks_only:
print("ERROR: -K and -U are mutually exclusive. If you want " +
"both stacks, that is the default.")
exit()
if not self.args.kernel_stacks_only and not self.args.user_stacks_only:
self.kernel_stack = True
self.user_stack = True
else:
self.kernel_stack = self.args.kernel_stacks_only
self.user_stack = self.args.user_stacks_only
# For tracing single processes in isolation, explicitly set perpid
# to True, if not already set. This is required to generate the correct
# BPF program that can store pid in the tgid field of the key\_t object.
if self.args.pid is not None and self.args.pid > 0:
self.args.perpid = True
self.probe = Probe(self.args.pattern,
self.kernel_stack, self.user_stack,
self.args.regexp, self.args.pid, self.args.perpid, self.args.cpu)
self.need_delimiter = self.args.delimited and not (
self.args.kernel_stacks_only or self.args.user_stacks_only)
def \_print\_kframe(self, addr):
print(" ", end="")
if self.args.verbose:
print("%-16x " % addr, end="")
if self.args.offset:
print("%s" % self.probe.bpf.ksym(addr, show_offset=True))
else:
print("%s" % self.probe.bpf.ksym(addr))
def \_print\_uframe(self, addr, pid):
print(" ", end="")
if self.args.verbose:
print("%-16x " % addr, end="")
if self.args.offset:
print("%s" % self.probe.bpf.sym(addr, pid, show_offset=True))
else:
print("%s" % self.probe.bpf.sym(addr, pid))
@staticmethod
def \_signal\_ignore(signal, frame):
print()
def \_print\_comm(self, comm, pid):
print(" %s [%d]" % (comm, pid))
def run(self):
self.probe.load()
self.probe.attach()
if not self.args.folded:
print("Tracing %d functions for \"%s\"... Hit Ctrl-C to end." %