哪些网站专做自媒体的,网站如何做页数,网站建设广告方案,漳州正规网站建设写在前面 博文内容涉及BCC工具 funccount 认知funccount 可以帮助用户追踪和分析Linux系统上特定函数、系统探针或USDT探针的运行次数。这对于性能分析、故障排查和系统优化等场景非常有用。理解不足小伙伴帮忙指正 #x1f603;,生活加油 不必太纠结于当下#xff0c;也不必…写在前面 博文内容涉及BCC工具 funccount 认知funccount 可以帮助用户追踪和分析Linux系统上特定函数、系统探针或USDT探针的运行次数。这对于性能分析、故障排查和系统优化等场景非常有用。理解不足小伙伴帮忙指正 ,生活加油 不必太纠结于当下也不必太忧虑未来当你经历过一些事情的时候眼前的风景已经和从前不一样了。——村上春树 funccount 是什么
funccount(8) 是BCC对事件,特别是函数调用进行计数的一个工具可以使用它回答以下问题:
某个内核态或用户态函数是否被调用过?
┌──[rootliruilongs.github.io]-[~]
└─$funccount tcp_send_fin
Tracing 1 functions for btcp_send_fin... Hit Ctrl-C to end.
^C
FUNC COUNT
tcp_send_fin 8
Detaching...
┌──[rootliruilongs.github.io]-[~]
└─$这将统计 tcp_send_fin 函数的调用次数。如果输出显示调用次数大于 0,那就说明这个函数确实被调用过。
该函数每秒被调用了多少次?
┌──[rootliruilongs.github.io]-[~]
└─$funccount -d 10 tcp_send_fin
Tracing 1 functions for btcp_send_fin... Hit Ctrl-C to end.FUNC COUNT
tcp_send_fin 6
Detaching...
┌──[rootliruilongs.github.io]-[~]
└─$这将每 10 秒钟输出一次 tcp_send_fin 函数的调用统计信息。从输出中我们可以计算出每秒被调用的次数,这就是该函数的调用频率。
funccount 用于自动执行 ftrace 的简单脚本。它只做一件事内核函数计数
需要说明的是并不是所有的函数都可以统计判断内核函数是否可以被计数,需要检查是否在下面两个文件中。
# 内核函数
$cat /proc/kallsyms
# ftrace 可以跟踪的内容
$cat /sys/kernel/debug/tracing/available_filter_functions 整体上可以分为 三类
动态跟踪的 内核探针和用户探针静态跟踪的 系统探针
常见的应用场景
跟踪 TCP 发送函数的调用次数
观察 TCP 发送相关函数的调用频率可以通过下面的方式
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$funccount -i 2 -d 10 tcp_send_*
Tracing 13 functions for btcp_send_*... Hit Ctrl-C to end.FUNC COUNT
tcp_send_fin 1
tcp_send_delayed_ack 1
tcp_send_ack 2
tcp_send_mss 3FUNC COUNT
tcp_send_mss 3FUNC COUNT
tcp_send_fin 1
tcp_send_delayed_ack 1
tcp_send_ack 2
tcp_send_mss 4FUNC COUNT
tcp_send_ack 1
tcp_send_mss 3FUNC COUNT
tcp_send_fin 2
tcp_send_delayed_ack 2
tcp_send_ack 4
tcp_send_mss 6
Detaching...
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$tcp_send_fin: 此函数用于发送 TCP 连接终止请求FIN 包。当一方完成数据传输并想要关闭连接时会发送 FIN 包。收到 FIN 包的一方也会发送一个 ACK 确认包然后可能继续发送剩余的数据最后发送自己的 FIN 包来关闭连接。
tcp_send_delayed_ack: 此函数用于发送 TCP 延迟确认ACK包。在某些情况下TCP 协议允许在接收到数据包后不立即发送 ACK 确认包而是等待一段时间称为“延迟 ACK”。此函数负责在适当的时机发送这些延迟 ACK。
tcp_send_ack: 此函数用于发送 TCP 确认ACK包。当接收到数据包时TCP 协议要求发送方发送一个 ACK 确认包以通知接收方已成功接收数据。此函数负责发送这些 ACK 确认包。
tcp_send_mss: 此函数用于发送 TCP 最大分段大小MSS选项。MSS 是 TCP 分段中可携带的最大有效载荷。在建立连接时双方会交换 MSS 值以便在传输过程中选择合适的分段大小。此函数负责在 SYN 包中发送本地系统的 MSS 值。
跟踪文件读写函数的调用次数
┌──[rootliruilongs.github.io]-[~]
└─$funccount -i 2 -d 5 vfs_read|vfs_write
Tracing 2 functions for bvfs_read|vfs_write... Hit Ctrl-C to end.FUNC COUNT
vfs_write 197
vfs_read 451FUNC COUNT
vfs_write 320
vfs_read 3553FUNC COUNT
vfs_read 28
vfs_write 85
Detaching...
┌──[rootliruilongs.github.io]-[~]
└─$vfs_read: 此函数负责在内核空间执行文件读取操作。vfs_write: 此函数负责在内核空间执行文件写入操作
跟踪网络套接字创建和销毁函数的调用次数
┌──[rootliruilongs.github.io]-[~]
└─$funccount -i 25 -d 25 sock_create|sock_release
Tracing 2 functions for bsock_create|sock_release... Hit Ctrl-C to end.FUNC COUNT
Detaching...跟踪进程创建和退出的函数调用次数
┌──[rootliruilongs.github.io]-[~]
└─$funccount -i 25 -d 25 do_fork|do_exit
Tracing 1 functions for bdo_fork|do_exit... Hit Ctrl-C to end.FUNC COUNT
do_exit 141
Detaching...单行程序
对虚拟文件系统 VFS 内核函数进行计数
funccount vfs_*对 TCP 内核函数进行计数
funccount tcp_*统计每秒 TCP 发送函数的调用次数
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$funccount -i 2 -d 4 tcp_send_*
Tracing 13 functions for btcp_send_*... Hit Ctrl-C to end.FUNC COUNT
tcp_send_mss 1
tcp_send_ack 3
tcp_send_delayed_ack 6FUNC COUNT
tcp_send_fin 1
tcp_send_delayed_ack 1
tcp_send_mss 4
tcp_send_ack 5
Detaching...
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$展示每秒块 IO 事件的数量
funccount -i 1 t:block;*展示每秒新创建的进程数量
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$funccount -i 1 t:sched:sched_process_fork -d 5
Tracing 1 functions for bt:sched:sched_process_fork... Hit Ctrl-C to end.FUNC COUNT
sched:sched_process_fork 9FUNC COUNT
sched:sched_process_fork 15FUNC COUNT
sched:sched_process_fork 2FUNC COUNT
sched:sched_process_fork 1FUNC COUNT
sched:sched_process_fork 1
Detaching...
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$展示每秒 libc中getaddrinfo()(域名解析)函数的调用次数
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$funccount -i 25 -d 25 c:getaddrinfo
Tracing 1 functions for bc:getaddrinfo... Hit Ctrl-C to end.FUNC COUNT
getaddrinfo 14
Detaching...
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$对 libc 中 malloc() 调用进行计数:
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools/doc]
└─$funccount c:malloc
Tracing 1 functions for bc:malloc... Hit Ctrl-C to end.
^C
FUNC COUNT
malloc 18168
Detaching...funccount 的 Demo
funccount 的语法
funccount(8)的命令行参数包括可以用来改变行为的选项以及一个描述被插桩事件的字符串:
funccount [opention] eventnameeventname 的语法是:
name 或者 p:name:对内核函数 name() 进行插桩。lib:name 或者 p:1ib:name:对用户态 lib 库中的函数 name() 进行插桩。Path:name:对位于 path 路径下文件中的用户态函数 name() 进行插桩。t:system:name:对名为 system:name 的内核跟踪点进行插桩。u:lib:name:对 lib 库中名为 name 的 USDT 探针进行插桩。*:用来匹配任意字符的通配符。-r 选项允许使用正则表达式。 func -- probe a kernel functionlib:func -- probe a user-space function in the library lib/path:func -- probe a user-space function in binary /pathp::func -- same thing as funcp:lib:func -- same thing as lib:funct:cat:event -- probe a kernel tracepointu:lib:probe -- probe a USDT tracepoint帮助文档Demo
帮助文档
liruilongercloudshell:~/bcc/tools$ cat funccount_example.txtfunccount_example.txt 这个文档描述了 funccount 这个 eBPF/bcc 工具的使用方法和功能。
指定一个模式(正则表达式或*通配符),追踪匹配的 函数/tracepoints 调用
# ./funccount vfs_*
Tracing... Ctrl-C to end.
^C
FUNC COUNT
vfs_create 1
vfs_rename 1
vfs_fsync_range 2
vfs_lock_file 30
vfs_fstatat 152
vfs_fstat 154
vfs_write 166
vfs_getattr_nosec 262
vfs_getattr 262
vfs_open 264
vfs_read 470
Detaching...上面的输出显示在跟踪vfsread()函数时调用了 470 次vfs_open() 264 次等等。
这对于探索内核代码非常有用可以找出哪些函数正在使用哪些没有使用。这可以将调查范围缩小到几个功能这些功能的计数与所调查的工作负载类似。
统计 vfs 函数调用次数
统计所有 tcp 相关函数调用次数
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$./funccount tcp_*
Tracing 397 functions for btcp_*... Hit Ctrl-C to end.
^C
FUNC COUNT
tcp_delack_timer_handler 1
tcp_leave_memory_pressure 1
tcp_delack_timer 2
tcp_write_timer 3
。。。。。。。。。。。。。。。。
tcp_release_cb 972
tcp_poll 1079
tcp_mstamp_refresh 1414
Detaching...
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─设置统计间隔和最大时间限制,每一秒钟采样持续时间为5s
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$./funccount -i 1 -d 5 vfs_read
Tracing 1 functions for bvfs_read... Hit Ctrl-C to end.FUNC COUNT
vfs_read 356FUNC COUNT
vfs_read 90FUNC COUNT
vfs_read 82FUNC COUNT
vfs_read 3478
^C
FUNC COUNT
vfs_read 13
Detaching...
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$过滤指定进程 ID 下的函数调用
./funccount -p 1442 contentions:*使用正则表达式匹配名称
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$./funccount -r c:(write|read)$ -d 5
Tracing 2 functions for bc:(write|read)$... Hit Ctrl-C to end.FUNC COUNT
read 233
write 388
Detaching...
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$统计指定内核态 tracepoint 静态跟踪事件调用次数
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$./funccount t:block:* -d 3
Tracing 21 functions for bt:block:*... Hit Ctrl-C to end.FUNC COUNT
block:block_plug 6
block:block_unplug 6
block:block_getrq 10
block:block_rq_insert 12
block:block_io_done 12
block:block_rq_complete 13
block:block_rq_issue 13
block:block_io_start 13
block:block_bio_remap 20
block:block_bio_backmerge 23
block:block_bio_queue 34
block:block_dirty_buffer 125
block:block_touch_buffer 145
Detaching...
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$统计用户态 USDT 探针静态跟踪调用次数
./funccount u:pthread:mutex -p 1442动态查看指定函数调用变化每秒统计一次数据信息
./funccount -i 1 vfs_*统计单个函数指定时间内调用次数
./funccount -d 5 vfs_read过滤指定 CPU 下的函数调用
funccount.py -i 1 -c 1 lapic_next_deadlinefuncccount 常见报错
┌──[rootliruilongs.github.io]-[~]
└─$funccount -i 2 -d 10 vfs_read,vfs_write
No functions matched by pattern b^vfs_read,vfs_write$
┌──[rootliruilongs.github.io]-[~]
└─$funccount u:pthread:mutex
USDT failed to instrument path b/lib64/libpthread.so.0
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$funccount -i 1 t:block;*
Cant mix strings and bytes in path components
┌──[rootliruilongs.github.io]-[~]
└─$funccount go:os.*
Cant mix strings and bytes in path componentsfunccount 源码
执行流程
funccount 脚本的工作原理
解析命令行参数了解用户想要追踪的函数、系统探点或USDT探针的模式以及其他设置如PID、时间间隔等。根据用户提供的信息创建一个Probe实例。Probe实例负责处理具体的探针如内核探针、用户空间探针等。使用Probe实例的方法将BPF程序附加到相应的探针上以便在探针被触发时收集数据。运行一个循环周期性地收集和显示探针的计数数据。这将持续到用户按下Ctrl-C或达到指定的持续时间。在循环结束时脚本会从探针上卸载BPF程序并退出。
源码
#!/usr/bin/env python
# lint-avoid-python-3-compatibility-imports
#
# funccount Count functions, tracepoints, and USDT probes.
# For Linux, uses BCC, eBPF.
#
# USAGE: funccount [-h] [-p PID] [-i INTERVAL] [-d DURATION] [-T] [-r]
# [-c CPU] pattern
#
# The pattern is a string with optional * wildcards, similar to file
# globbing. If youd prefer to use regular expressions, use the -r option.
#
# Copyright (c) 2015 Brendan Gregg.
# Licensed under the Apache License, Version 2.0 (the License)
#
# 09-Sep-2015 Brendan Gregg Created this.
# 18-Oct-2016 Sasha Goldshtein Generalized for uprobes, tracepoints, USDT.from __future__ import print_function
from bcc import ArgString, BPF, USDT
from time import sleep, strftime
import argparse
import re
import signal
import sys
import tracebackdebug Falsedef verify_limit(num):# 用于检查探测器的数量是否超过了内核的限制probe_limit BPF.get_probe_limit()if num probe_limit:raise Exception(maximum of %d probes allowed, attempted %d %(probe_limit, num))# 实际的观测类
class Probe(object):# 初始化跟踪对象def __init__(self, pattern, use_regexFalse, pidNone, cpuNone):Init a new probe.Init the probe from the pattern provided by the user. The supportedpatterns mimic the trace and argdist tools, but are simpler becausewe dont have to distinguish between probes and retprobes.func -- probe a kernel functionlib:func -- probe a user-space function in the library lib/path:func -- probe a user-space function in binary /pathp::func -- same thing as funcp:lib:func -- same thing as lib:funct:cat:event -- probe a kernel tracepointu:lib:probe -- probe a USDT tracepoint# 跟踪的表达式进行解析,可以看到这里都是字节操作# 基于分隔符b:拆分字节串得到一个部分列表。parts bytes(pattern).split(b:)# 如果只有一个部分意味着缺少类型因此将类型设置为bp表示“模式”库设置为空字节串,if len(parts) 1:# 也就是上面 func 》 p::func 部分parts [bp, b, parts[0]]# 如果有两个切片将类型设置为bp并将第一个部分作为库第二个部分作为模式 elif len(parts) 2:# lib:func 》 p:lib:func parts [bp, parts[0], parts[1]]# 如果有三个 elif len(parts) 3:# 第一个切片为 t 为内核跟踪点if parts[0] bt:parts [bt, b, b%s:%s % tuple(parts[1:])]# 如果不为 t p u 那么抛异常if parts[0] not in [bp, bt, bu]: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)# 从 parts 中解构 三个变量(self.type, self.library, self.pattern) parts# use_regex 为初始化方法参数是否使用正则如果不使用use_regex为False需要转化为正则if not use_regex:self.pattern self.pattern.replace(b*, b.*)self.pattern b^ self.pattern b$# 如果类型是bp且指定了库或者类型是bu,# 尝试使用BPF.find_library()方法找到库路径。如果找不到# 尝试使用BPF.find_exe()方法找到可执行文件if (self.type bp and self.library) or self.type bu:libpath BPF.find_library(self.library)if libpath is None:# This might be an executable (e.g. bash)libpath BPF.find_exe(str(self.library))if libpath is None or len(libpath) 0:raise Exception(unable to find library %s % self.library)self.library libpathself.pid pidself.cpu cpuself.matched 0self.trace_functions {} # map location number to function name# 是否为内核探针def is_kernel_probe(self):return self.type bt or (self.type bp and self.library b)def attach(self):#self.type表示要附加的探针类型# p内核探针# t系统探针# u用户探针if self.type bp and not self.library:# 动态跟踪# 为self.trace_functions中的每个函数附加一个内核探针。# 使用self.bpf.attach_kprobe()方法将BPF程序附加到内核探针事件。for index, function in self.trace_functions.items():self.bpf.attach_kprobe(eventfunction,fn_nametrace_count_%d % index)elif self.type bp and self.library:# 动态跟踪# 为self.trace_functions中的每个函数附加一个用户空间探针。# 使用self.bpf.attach_uprobe()方法将BPF程序附加到用户空间探针事件。for index, function in self.trace_functions.items():self.bpf.attach_uprobe(nameself.library,symfunction,fn_nametrace_count_%d % index,pidself.pid or -1)elif self.type bt:# 静态跟踪# 为self.trace_functions中的每个函数附加一个系统探针。# 使用self.bpf.attach_tracepoint()方法将BPF程序附加到系统探针事件for index, function in self.trace_functions.items():self.bpf.attach_tracepoint(tpfunction,fn_nametrace_count_%d % index)elif self.type bu:# 如果self.type为u用户探针则不执行任何操作因为在load方法中已经附加了用户探针。pass # Nothing to do -- attach already happened in loaddef _add_function(self, template, probe_name):# 表示要添加到bpf程序中的内核探针函数的名称。# 根据给定的模板和探针名称生成新的BPF函数并将这些函数添加到bpf程序中new_func btrace_count_%d % self.matchedtext template.replace(bPROBE_FUNCTION, new_func)text text.replace(bLOCATION, b%d % self.matched)self.trace_functions[self.matched] probe_nameself.matched 1return textdef _generate_functions(self, template):# template 为上面拼接的字符串即C代码self.usdt Nonetext bif self.type bp and not self.library:# get_kprobe_functions 函数接收一个模式字符串并返回与该模式匹配的所有内核探针函数的列表functions BPF.get_kprobe_functions(self.pattern)# 校验跟踪的内核探针函数数量verify_limit(len(functions))for function in functions:text self._add_function(template, function)elif self.type bp and self.library:# uprobes are tricky because the same function may have multiple# addresses, and the same address may be mapped to multiple# functions. We arent allowed to create more than one uprobe# per address, so track unique addresses and ignore functions that# map to an address that weve already seen. Also ignore functions# that may repeat multiple times with different addresses.addresses, functions (set(), set())functions_and_addresses BPF.get_user_functions_and_addresses(self.library, self.pattern)verify_limit(len(functions_and_addresses))for function, address in functions_and_addresses:if address in addresses or function in functions:continueaddresses.add(address)functions.add(function)text self._add_function(template, function)elif self.type bt:tracepoints BPF.get_tracepoints(self.pattern)verify_limit(len(tracepoints))for tracepoint in tracepoints:text self._add_function(template, tracepoint)elif self.type bu:self.usdt USDT(pathstr(self.library), pidself.pid)matches []for probe in self.usdt.enumerate_probes():if not self.pid and (probe.bin_path ! self.library):continueif re.match(self.pattern, probe.name):matches.append(probe.name)verify_limit(len(matches))for match in matches:new_func btrace_count_%d % self.matchedtext self._add_function(template, match)self.usdt.enable_probe(match, new_func)if debug:print(self.usdt.get_text())return textdef load(self):Desc : 更具给定的探针配置加载 eBPF 程序trace_count_text b
int PROBE_FUNCTION(void *ctx) {FILTERPIDFILTERCPUint loc LOCATION;counts.atomic_increment(loc);return 0;
}bpf_text b#include uapi/linux/ptrace.hBPF_ARRAY(counts, u64, NUMLOCATIONS);# We really mean the tgid from the kernels perspective, which is in# the top 32 bits of bpf_get_current_pid_tgid().if self.pid:trace_count_text trace_count_text.replace(bFILTERPID,bu32 pid bpf_get_current_pid_tgid() 32;if (pid ! %d) { return 0; } % self.pid)else:trace_count_text trace_count_text.replace(bFILTERPID, b)if self.cpu:trace_count_text trace_count_text.replace(bFILTERCPU,bu32 cpu bpf_get_smp_processor_id();if (cpu ! %d) { return 0; } % int(self.cpu))else:trace_count_text trace_count_text.replace(bFILTERCPU, b)bpf_text self._generate_functions(trace_count_text)bpf_text bpf_text.replace(bNUMLOCATIONS,b%d % len(self.trace_functions))if debug:print(bpf_text)if self.matched 0:raise Exception(No functions matched by pattern %s %self.pattern)self.bpf BPF(textbpf_text,usdt_contexts[self.usdt] if self.usdt else [])self.clear() # Initialize all array items to zerodef counts(self):return self.bpf[counts]def clear(self):counts self.bpf[counts]for location, _ in list(self.trace_functions.items()):counts[counts.Key(location)] counts.Leaf()class Tool(object):def __init__(self) - None:# 工具的使用 Demo 说明examples examples:./funccount vfs_* # count kernel fns starting with vfs./funccount -r ^vfs.* # same as above, using regular expressions./funccount -Ti 5 vfs_* # output every 5 seconds, with timestamps./funccount -d 10 vfs_* # trace for 10 seconds only./funccount -p 185 vfs_* # count vfs calls for PID 181 only./funccount t:sched:sched_fork # count calls to the sched_fork tracepoint./funccount -p 185 u:node:gc* # count all GC USDT probes in node, PID 185./funccount c:malloc # count all malloc() calls in libc./funccount go:os.* # count all os.* calls in libgo./funccount -p 185 go:os.* # count all os.* calls in libgo, PID 185./funccount ./test:read* # count read* calls in the ./test binary./funccount -c 1 vfs_* # count vfs calls on CPU 1 only#argparse 用于帮助输出以及参数解析parser argparse.ArgumentParser(descriptionCount functions, tracepoints, and USDT probes,formatter_classargparse.RawDescriptionHelpFormatter,epilogexamples)parser.add_argument(-p, --pid, typeint,helptrace this PID only)parser.add_argument(-i, --interval,helpsummary interval, seconds)parser.add_argument(-d, --duration,helptotal duration of trace, seconds)parser.add_argument(-T, --timestamp, actionstore_true,helpinclude timestamp on output)parser.add_argument(-r, --regexp, actionstore_true,helpuse regular expressions. Default is \*\ wildcards only.)parser.add_argument(-D, --debug, actionstore_true,helpprint BPF program before starting (for debugging purposes))parser.add_argument(-c, --cpu,helptrace this CPU only)parser.add_argument(pattern,typeArgString,helpsearch expression for events)# 获取到解析的参数self.args parser.parse_args()global debugdebug self.args.debug# 初始化跟踪对象跟踪对象用于执行实际的 BPF 操作self.probe Probe(self.args.pattern, self.args.regexp, self.args.pid,self.args.cpu)# 这里可以看到获得到的命令行参数包括 跟踪的表达式正则匹配式进程ID 已经 CPU 编号# 如果 有 -d 没有 -i 。那么 -i 配置成 -d 的纸 if self.args.duration and not self.args.interval:self.args.interval self.args.duration# 如果 -i 没有设置默认是 99999999if not self.args.interval:self.args.interval 99999999staticmethoddef _signal_ignore(signal, frame):print()def run(self):# 调用self.probe.load()方法加载eBPF程序self.probe.load()# 调用self.probe.attach()方法将eBPF程序附加到相应的目标上。self.probe.attach()print(Tracing %d functions for \%s\... Hit Ctrl-C to end. %(self.probe.matched, bytes(self.args.pattern)))# 初始化exiting变量以控制程序的退出exiting 0 if self.args.interval else 1# 初始化seconds变量为0用于跟踪程序的运行时间seconds 0# 进入无限循环直到exiting变量变为1while True:try:sleep(int(self.args.interval))seconds int(self.args.interval)except KeyboardInterrupt:exiting 1# as cleanup can take many seconds, trap Ctrl-C:signal.signal(signal.SIGINT, Tool._signal_ignore) # type: ignoreif self.args.duration and seconds int(self.args.duration):exiting 1print()if self.args.timestamp:print(%-8s\n % strftime(%H:%M:%S), end)print(%-36s %8s % (FUNC, COUNT))counts self.probe.counts()for k, v in sorted(counts.items(),keylambda counts: counts[1].value):if v.value 0:continueprint(%-36s %8d %(self.probe.trace_functions[k.value].decode(utf-8, replace), v.value))if exiting:print(Detaching...)exit()else:self.probe.clear()if __name__ __main__:try:Tool().run()except Exception:if debug:traceback.print_exc()elif sys.exc_info()[0] is not SystemExit:print(sys.exc_info()[1]) 对源码进行简单分析
方法和类说明
Probe 类提供了用于创建、配置和附加 eBPF 探测对象的方法。 __init__初始化一个新的探测对象。解析用户提供的模式、PID 和 CPU 参数。根据模式类型内核函数、用户空间函数、跟踪点或 USDT 探针设置探测对象的属性。 is_kernel_probe检查探测对象是否为内核探测。如果探测类型为 t跟踪点或者是类型为 p用户空间或内核函数且库名称为空表示内核函数则返回 True。 attach将探测对象附加到目标上。根据探测类型内核函数、用户空间函数、跟踪点或 USDT 探针使用 BCC 库将 BPF 程序附加到相应的目标上。 _add_function向 BPF 程序模板中添加新的探测函数。这个方法根据给定的模板和探测名称生成一个新的探测函数并将其添加到 BPF 程序文本中。同时将新函数的索引和名称添加到 trace_functions 字典中。 _generate_functions根据探测类型和模式生成 BPF 程序文本。这个方法根据探测类型内核函数、用户空间函数、跟踪点或 USDT 探针和模式生成相应的 BPF 程序文本。对于用户空间函数和 USDT 探针还需要处理多个地址和重复函数的问题。 load加载 BPF 程序。这个方法首先定义了一个基本的 BPF 程序模板然后根据探测类型和模式生成具体的 BPF 程序文本。接着使用 BCC 库将 BPF 程序加载到内核中。最后初始化所有计数器数组项为零。 counts返回 BPF 程序的计数器数组。这个方法返回一个字典其中键是探测位置的索引值是对应的计数值。 clear清除所有计数器数组项。这个方法遍历 trace_functions 字典将所有计数器数组项的值重置为零。
Tool 类是 funccount 脚本的主体部分它负责解析命令行参数、创建探测对象、加载 BPF 程序、附加探测并定期输出计数结果 __init__初始化 Tool 对象。设置命令行参数和示例使用 argparse 解析命令行参数如目标进程的 PID、采样间隔、持续时间、是否使用正则表达式匹配函数名等。然后根据解析得到的参数创建一个 Probe 对象。 _signal_ignore静态方法用于忽略 CtrlC 信号。这在脚本运行期间捕获 CtrlC 时很有用因为它允许脚本在退出前完成清理工作。 run运行 funccount 工具。首先调用 probe.load() 加载 BPF 程序然后调用 probe.attach() 将探测附加到目标上。接着进入一个循环定期输出当前的计数结果。循环将持续到达到指定的持续时间或用户按下 CtrlC。在循环中根据参数设置输出计数结果包括时间戳、函数名和调用次数。如果达到持续时间或用户按下 CtrlC脚本将停止统计卸载 BPF 程序并输出最后一次的计数结果。
Tool 类提供了运行 funccount 工具所需的主要功能。
可以通过 dubg 的方式看到生成的 C 代码通过代码可以看到总共跟踪了 17 个内核函数
┌──[rootliruilongs.github.io]-[/usr/share/bcc/tools]
└─$funccount tcp_*_send* -D test.cppcounts.atomic_increment(loc) 将counts数组中索引为loc的元素的值加1。这样每当发生与loc相关联的探针或事件时counts数组中相应的计数就会递增。
#include uapi/linux/ptrace.hBPF_ARRAY(counts, u64, 17);int trace_count_0(void *ctx)
{int loc 0;counts.atomic_increment(loc);return 0;
}int trace_count_1(void *ctx)
{int loc 1;counts.atomic_increment(loc);return 0;
}int trace_count_2(void *ctx)
{int loc 2;counts.atomic_increment(loc);return 0;
}
..........................
博文部分内容参考
© 文中涉及参考链接内容版权归原作者所有如有侵权请告知 《BPF Performance Tools》 © 2018-2024 liruilongergmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)