BPF:BCC工具 funccount 统计内核函数调用(内核函数、跟踪点USDT探针)认知

写在前面


  • 博文内容涉及BCC工具 funccount 认知
  • funccount 可以帮助用户追踪和分析Linux系统上特定函数、系统探针或USDT探针的运行次数。
  • 这对于性能分析、故障排查和系统优化等场景非常有用。
  • 理解不足小伙伴帮忙指正 😃,生活加油

不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树


funccount 是什么?

funccount(8) 是BCC对事件,特别是函数调用进行计数的一个工具,可以使用它回答以下问题:

  • 某个内核态或用户态函数是否被调用过?
┌──[root@liruilongs.github.io]-[~] 
└─$funccount tcp_send_fin
Tracing 1 functions for "b'tcp_send_fin'"... Hit Ctrl-C to end.
^C
FUNC                                    COUNT
tcp_send_fin                                8
Detaching...
┌──[root@liruilongs.github.io]-[~] 
└─$

这将统计 tcp_send_fin 函数的调用次数。如果输出显示调用次数大于 0,那就说明这个函数确实被调用过。

  • 该函数每秒被调用了多少次?
┌──[root@liruilongs.github.io]-[~] 
└─$funccount -d 10 tcp_send_fin
Tracing 1 functions for "b'tcp_send_fin'"... Hit Ctrl-C to end.

FUNC                                    COUNT
tcp_send_fin                                6
Detaching...
┌──[root@liruilongs.github.io]-[~] 
└─$

这将每 10 秒钟输出一次 tcp_send_fin 函数的调用统计信息。从输出中我们可以计算出每秒被调用的次数,这就是该函数的调用频率。

funccount 用于自动执行 ftrace 的简单脚本。它只做一件事:内核函数计数

需要说明的是,并不是所有的函数都可以统计,判断内核函数是否可以被计数,需要检查是否在下面两个文件中。

# 内核函数
$cat /proc/kallsyms
# ftrace 可以跟踪的内容
$cat /sys/kernel/debug/tracing/available_filter_functions 

整体上可以分为 三类:

  • 动态跟踪的 内核探针用户探针
  • 静态跟踪的 系统探针

常见的应用场景

跟踪 TCP 发送函数的调用次数

观察 TCP 发送相关函数的调用频率,可以通过下面的方式

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$funccount  -i 2  -d 10 "tcp_send_*"
Tracing 13 functions for "b'tcp_send_*'"... Hit Ctrl-C to end.

FUNC                                    COUNT
tcp_send_fin                                1
tcp_send_delayed_ack                        1
tcp_send_ack                                2
tcp_send_mss                                3

FUNC                                    COUNT
tcp_send_mss                                3

FUNC                                    COUNT
tcp_send_fin                                1
tcp_send_delayed_ack                        1
tcp_send_ack                                2
tcp_send_mss                                4

FUNC                                    COUNT
tcp_send_ack                                1
tcp_send_mss                                3

FUNC                                    COUNT
tcp_send_fin                                2
tcp_send_delayed_ack                        2
tcp_send_ack                                4
tcp_send_mss                                6
Detaching...
┌──[root@liruilongs.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 值。

跟踪文件读写函数的调用次数

┌──[root@liruilongs.github.io]-[~] 
└─$funccount  -i 2 -d 5 'vfs_read|vfs_write'
Tracing 2 functions for "b'vfs_read|vfs_write'"... Hit Ctrl-C to end.

FUNC                                    COUNT
vfs_write                                 197
vfs_read                                  451

FUNC                                    COUNT
vfs_write                                 320
vfs_read                                 3553

FUNC                                    COUNT
vfs_read                                   28
vfs_write                                  85
Detaching...
┌──[root@liruilongs.github.io]-[~] 
└─$
  • vfs_read: 此函数负责在内核空间执行文件读取操作。
  • vfs_write: 此函数负责在内核空间执行文件写入操作

跟踪网络套接字创建和销毁函数的调用次数

┌──[root@liruilongs.github.io]-[~] 
└─$funccount  -i 25 -d 25  "sock_create|sock_release"
Tracing 2 functions for "b'sock_create|sock_release'"... Hit Ctrl-C to end.

FUNC                                    COUNT
Detaching...

跟踪进程创建和退出的函数调用次数

┌──[root@liruilongs.github.io]-[~] 
└─$funccount  -i 25 -d 25 'do_fork|do_exit'
Tracing 1 functions for "b'do_fork|do_exit'"... Hit Ctrl-C to end.

FUNC                                    COUNT
do_exit                                   141
Detaching...

单行程序

对虚拟文件系统 VFS 内核函数进行计数

funccount 'vfs_*'

对 TCP 内核函数进行计数

funccount "tcp_*"

统计每秒 TCP 发送函数的调用次数

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$funccount  -i 2  -d 4 "tcp_send_*"
Tracing 13 functions for "b'tcp_send_*'"... Hit Ctrl-C to end.

FUNC                                    COUNT
tcp_send_mss                                1
tcp_send_ack                                3
tcp_send_delayed_ack                        6

FUNC                                    COUNT
tcp_send_fin                                1
tcp_send_delayed_ack                        1
tcp_send_mss                                4
tcp_send_ack                                5
Detaching...
┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$

展示每秒块 IO 事件的数量

funccount -i 1 't:block;*

展示每秒新创建的进程数量

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$funccount -i 1 t:sched:sched_process_fork -d 5
Tracing 1 functions for "b't:sched:sched_process_fork'"... Hit Ctrl-C to end.

FUNC                                    COUNT
sched:sched_process_fork                    9

FUNC                                    COUNT
sched:sched_process_fork                   15

FUNC                                    COUNT
sched:sched_process_fork                    2

FUNC                                    COUNT
sched:sched_process_fork                    1

FUNC                                    COUNT
sched:sched_process_fork                    1
Detaching...
┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$

展示每秒 libcgetaddrinfo()(域名解析)函数的调用次数

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$funccount -i 25 -d 25 "c:getaddrinfo"
Tracing 1 functions for "b'c:getaddrinfo'"... Hit Ctrl-C to end.

FUNC                                    COUNT
getaddrinfo                                14
Detaching...
┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$

libcmalloc() 调用进行计数:

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools/doc] 
└─$funccount c:malloc
Tracing 1 functions for "b'c:malloc'"... Hit Ctrl-C to end.
^C
FUNC                                    COUNT
malloc                                  18168
Detaching...

funccount 的 Demo

funccount 的语法

funccount(8)的命令行参数包括可以用来改变行为的选项,以及一个描述被插桩事件的字符串:

funccount [opention] eventname

eventname 的语法是:

  • 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 function
            lib:func        -- probe a user-space function in the library 'lib'
            /path:func      -- probe a user-space function in binary '/path'
            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

帮助文档Demo

帮助文档

liruilonger@cloudshell:~/bcc/tools$ cat funccount_example.txt

funccount_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 相关函数调用次数

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$./funccount 'tcp_*'
Tracing 397 functions for "b'tcp_*'"... 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...
┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─

设置统计间隔和最大时间限制,每一秒钟采样,持续时间为5s

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$./funccount -i 1 -d 5 vfs_read
Tracing 1 functions for "b'vfs_read'"... Hit Ctrl-C to end.

FUNC                                    COUNT
vfs_read                                  356

FUNC                                    COUNT
vfs_read                                   90

FUNC                                    COUNT
vfs_read                                   82

FUNC                                    COUNT
vfs_read                                 3478
^C
FUNC                                    COUNT
vfs_read                                   13
Detaching...
┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$

过滤指定进程 ID 下的函数调用

./funccount -p 1442 contentions:*

使用正则表达式匹配名称

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$./funccount -r 'c:(write|read)$' -d 5
Tracing 2 functions for "b'c:(write|read)$'"... Hit Ctrl-C to end.

FUNC                                    COUNT
read                                      233
write                                     388
Detaching...
┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$

统计指定内核态 tracepoint 静态跟踪事件调用次数

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$./funccount t:block:*  -d 3
Tracing 21 functions for "b't: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...
┌──[root@liruilongs.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_deadline

funcccount 常见报错

┌──[root@liruilongs.github.io]-[~] 
└─$funccount  -i 2 -d 10 'vfs_read,vfs_write'
No functions matched by pattern b'^vfs_read,vfs_write$'
┌──[root@liruilongs.github.io]-[~] 
└─$funccount u:pthread:mutex
USDT failed to instrument path b'/lib64/libpthread.so.0'
┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$funccount -i 1 't:block;*' 
Can't mix strings and bytes in path components
┌──[root@liruilongs.github.io]-[~] 
└─$funccount  go:os.*
Can't mix strings and bytes in path components

funccount 源码

执行流程

funccount 脚本的工作原理:

  1. 解析命令行参数,了解用户想要追踪的函数、系统探点或USDT探针的模式,以及其他设置(如PID、时间间隔等)。
  2. 根据用户提供的信息,创建一个Probe实例。Probe实例负责处理具体的探针(如内核探针、用户空间探针等)。
  3. 使用Probe实例的方法,将BPF程序附加到相应的探针上,以便在探针被触发时收集数据。
  4. 运行一个循环,周期性地收集和显示探针的计数数据。这将持续到用户按下Ctrl-C或达到指定的持续时间。
  5. 在循环结束时,脚本会从探针上卸载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 you'd 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 traceback

debug = False

def 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_regex=False, pid=None, 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'
            /path:func      -- probe a user-space function in binary '/path'
            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
        """
        # 跟踪的表达式进行解析,可以看到这里都是字节操作
        # 基于`分隔符b':'拆分字节串`,得到一个部分列表。
        parts = bytes(pattern).split(b':')
        # 如果只有一个部分,意味着缺少类型,因此将类型设置为b"p"(表示“模式”),库设置为空字节串,
        if len(parts) == 1:
            # 也就是上面 func ===》 `p::func` 部分
            parts = [b"p", b"", parts[0]]
        # 如果有两个切片,将类型设置为b"p",并将第一个部分作为库,第二个部分作为模式    
        elif len(parts) == 2:
            #  lib:func    ==》 `p:lib:func`` 
            parts = [b"p", parts[0], parts[1]]
        #  如果有三个    
        elif len(parts) == 3:
            # 第一个切片为 `t` 为内核跟踪点
            if parts[0] == b"t":
                parts = [b"t", b"", b"%s:%s" % tuple(parts[1:])]
            # 如果不为 t p u 那么抛异常
            if parts[0] not in [b"p", b"t", b"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)
        # 从 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'$'
        
        # 如果类型是b"p"且指定了库,或者类型是b"u",
        # 尝试使用BPF.find_library()方法找到库路径。如果找不到,
        # 尝试使用BPF.find_exe()方法找到可执行文件
        if (self.type == b"p" and self.library) or self.type == b"u":
            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 = libpath

        self.pid = pid
        self.cpu = cpu
        self.matched = 0
        self.trace_functions = {}   # map location number to function name
    # 是否为内核探针
    def is_kernel_probe(self):
        return self.type == b"t" or (self.type == b"p" and self.library == b"")

    def attach(self):
        #self.type表示要附加的探针类型
        # p(内核探针)
        # t(系统探针)
        # u(用户探针)
        if self.type == b"p" and not self.library:
            # 动态跟踪
            # 为self.trace_functions中的每个函数附加一个内核探针。
            # 使用self.bpf.attach_kprobe()方法将BPF程序附加到内核探针事件。
            for index, function in self.trace_functions.items():
                self.bpf.attach_kprobe(
                        event=function,
                        fn_name="trace_count_%d" % index)
        elif self.type == b"p" and self.library:
            # 动态跟踪
            # 为self.trace_functions中的每个函数附加一个用户空间探针。
            # 使用self.bpf.attach_uprobe()方法将BPF程序附加到用户空间探针事件。
            for index, function in self.trace_functions.items():
                self.bpf.attach_uprobe(
                        name=self.library,
                        sym=function,
                        fn_name="trace_count_%d" % index,
                        pid=self.pid or -1)
        elif self.type == b"t":
            # 静态跟踪
            # 为self.trace_functions中的每个函数附加一个系统探针。
            # 使用self.bpf.attach_tracepoint()方法将BPF程序附加到系统探针事件
            for index, function in self.trace_functions.items():
                self.bpf.attach_tracepoint(
                        tp=function,
                        fn_name="trace_count_%d" % index)
        elif self.type == b"u":
            # 如果self.type为u(用户探针),则不执行任何操作,因为在load方法中已经附加了用户探针。
            pass    # Nothing to do -- attach already happened in `load`

    def _add_function(self, template, probe_name):
        #  表示要添加到bpf程序中的内核探针函数的名称。
        # 根据给定的模板和探针名称生成新的BPF函数,并将这些函数添加到bpf程序中
        new_func = b"trace_count_%d" % self.matched
        text = template.replace(b"PROBE_FUNCTION", new_func)
        text = text.replace(b"LOCATION", b"%d" % self.matched)
        self.trace_functions[self.matched] = probe_name
        self.matched += 1
        return text

    def _generate_functions(self, template):
        # template 为上面拼接的字符串,即C代码
        self.usdt = None
        text = b""
        if self.type == b"p" 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 == b"p" 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 aren't allowed to create more than one uprobe
            # per address, so track unique addresses and ignore functions that
            # map to an address that we've 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:
                    continue
                addresses.add(address)
                functions.add(function)
                text += self._add_function(template, function)
        elif self.type == b"t":
            tracepoints = BPF.get_tracepoints(self.pattern)
            verify_limit(len(tracepoints))
            for tracepoint in tracepoints:
                text += self._add_function(template, tracepoint)
        elif self.type == b"u":
            self.usdt = USDT(path=str(self.library), pid=self.pid)
            matches = []
            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):
                    matches.append(probe.name)
            verify_limit(len(matches))
            for match in matches:
                new_func = b"trace_count_%d" % self.matched
                text += self._add_function(template, match)
                self.usdt.enable_probe(match, new_func)
            if debug:
                print(self.usdt.get_text())
        return text
    
    def load(self):
        """
        @Desc    :   更具给定的探针配置加载 eBPF 程序
        """
        
        trace_count_text = b"""
int PROBE_FUNCTION(void *ctx) {
    FILTERPID
    FILTERCPU
    int loc = LOCATION;
    counts.atomic_increment(loc);
    return 0;
}
        """
        bpf_text = b"""#include <uapi/linux/ptrace.h>

BPF_ARRAY(counts, u64, NUMLOCATIONS);
        """

        # 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.pid:
            trace_count_text = trace_count_text.replace(b'FILTERPID',
                b"""u32 pid = bpf_get_current_pid_tgid() >> 32;
                   if (pid != %d) { return 0; }""" % self.pid)
        else:
            trace_count_text = trace_count_text.replace(b'FILTERPID', b'')

        if self.cpu:
            trace_count_text = trace_count_text.replace(b'FILTERCPU',
                b"""u32 cpu = bpf_get_smp_processor_id();
                   if (cpu != %d) { return 0; }""" % int(self.cpu))
        else:
            trace_count_text = trace_count_text.replace(b'FILTERCPU', b'')

        bpf_text += self._generate_functions(trace_count_text)
        bpf_text = bpf_text.replace(b"NUMLOCATIONS",
                                    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(text=bpf_text,
                       usdt_contexts=[self.usdt] if self.usdt else [])
        self.clear()    # Initialize all array items to zero
   
    def 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(
            description="Count functions, tracepoints, and USDT probes",
            formatter_class=argparse.RawDescriptionHelpFormatter,
            epilog=examples)
        parser.add_argument("-p", "--pid", type=int,
            help="trace this PID 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("-D", "--debug", action="store_true",
            help="print BPF program before starting (for debugging purposes)")
        parser.add_argument("-c", "--cpu",
            help="trace this CPU only")
        parser.add_argument("pattern",
            type=ArgString,
            help="search expression for events")
        # 获取到解析的参数
        self.args = parser.parse_args()
        global debug
        debug = 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 没有设置,默认是 99999999
        if not self.args.interval:
            self.args.interval = 99999999

    @staticmethod
    def _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变量变为1:
        while 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: ignore
            if self.args.duration and seconds >= int(self.args.duration):
                exiting = 1

            print()
            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(),
                               key=lambda counts: counts[1].value):
                if v.value == 0:
                    continue
                print("%-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 探测对象的方法。

  1. __init__:初始化一个新的探测对象。解析用户提供的模式、PID 和 CPU 参数。根据模式类型(内核函数、用户空间函数、跟踪点或 USDT 探针),设置探测对象的属性。

  2. is_kernel_probe:检查探测对象是否为内核探测。如果探测类型为 t(跟踪点)或者是类型为 p(用户空间或内核函数)且库名称为空(表示内核函数),则返回 True

  3. attach:将探测对象附加到目标上。根据探测类型(内核函数、用户空间函数、跟踪点或 USDT 探针),使用 BCC 库将 BPF 程序附加到相应的目标上。

  4. _add_function:向 BPF 程序模板中添加新的探测函数。这个方法根据给定的模板和探测名称生成一个新的探测函数,并将其添加到 BPF 程序文本中。同时,将新函数的索引和名称添加到 trace_functions 字典中。

  5. _generate_functions:根据探测类型和模式生成 BPF 程序文本。这个方法根据探测类型(内核函数、用户空间函数、跟踪点或 USDT 探针)和模式,生成相应的 BPF 程序文本。对于用户空间函数和 USDT 探针,还需要处理多个地址和重复函数的问题。

  6. load:加载 BPF 程序。这个方法首先定义了一个基本的 BPF 程序模板,然后根据探测类型和模式生成具体的 BPF 程序文本。接着,使用 BCC 库将 BPF 程序加载到内核中。最后,初始化所有计数器数组项为零。

  7. counts:返回 BPF 程序的计数器数组。这个方法返回一个字典,其中键是探测位置的索引,值是对应的计数值

  8. clear:清除所有计数器数组项。这个方法遍历 trace_functions 字典,将所有计数器数组项的值重置为零。

Tool 类是 funccount 脚本的主体部分,它负责解析命令行参数、创建探测对象、加载 BPF 程序、附加探测并定期输出计数结果

  1. __init__:初始化 Tool 对象。设置命令行参数和示例,使用 argparse 解析命令行参数,如目标进程的 PID、采样间隔、持续时间、是否使用正则表达式匹配函数名等。然后,根据解析得到的参数创建一个 Probe 对象。

  2. _signal_ignore:静态方法,用于忽略 Ctrl+C 信号。这在脚本运行期间捕获 Ctrl+C 时很有用,因为它允许脚本在退出前完成清理工作。

  3. run:运行 funccount 工具。首先,调用 probe.load() 加载 BPF 程序,然后调用 probe.attach() 将探测附加到目标上。接着,进入一个循环,定期输出当前的计数结果。循环将持续到达到指定的持续时间或用户按下 Ctrl+C。在循环中,根据参数设置,输出计数结果,包括时间戳、函数名和调用次数。如果达到持续时间或用户按下 Ctrl+C,脚本将停止统计,卸载 BPF 程序,并输出最后一次的计数结果。

Tool 类提供了运行 funccount 工具所需的主要功能。

可以通过 dubg 的方式看到生成的 C 代码,通过代码可以看到,总共跟踪了 17 个内核函数,

┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] 
└─$funccount  'tcp_*_send*' -D > test.cpp

counts.atomic_increment(loc)counts数组中索引为loc的元素的值加1。这样,每当发生与loc相关联的探针或事件时,counts数组中相应的计数就会递增。

#include <uapi/linux/ptrace.h>

BPF_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 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/759087.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

gin 服务端无法使用sse流式nginx配置

我在本地使用 gin 可以流式的将大模型数据传递给前端。但是当我部署到服务器中时&#xff0c;会阻塞一段时间&#xff0c;然后显示一大段文本。 起初我怀疑是gin 没有及时将数据刷到管道中&#xff0c;但是经过测试&#xff0c;还是会阻塞。 c.Writer.(http.Flusher).Flush()最…

HTML5的多线程技术:Web Worker API

Web Workers API 是HTML5的一项技术&#xff0c;它允许在浏览器后台独立于主线程运行脚本&#xff0c;即允许进行多线程处理。这对于执行密集型计算任务特别有用&#xff0c;因为它可以防止这些任务阻塞用户界面&#xff0c;从而保持网页的响应性和交互性。Web Workers在自己的…

CAS服务端部署

部署CAS Cas服务端其实就是一个war包。 在资源\cas\source\cas-server-4.0.0-release\cas-server-4.0.0\modules目录下cas-server-webapp-4.0.0.war 将其改名为cas.war放入tomcat目录下的webapps下。启动tomcat自动解压war包。浏览器输入 登录页面 http://localhost:8080/ca…

43 - 部门工资前三高的所有员工(高频 SQL 50 题基础版)

43 - 部门工资前三高的所有员工 # dense_rank 排名selectDepartment,Employee,Salary from(selectd.name as Department,e.name as Employee,e.salary as Salary,(dense_rank() over (partition by d.name order by e.salary desc)) as rankingfrom Employee e left join Depar…

【设计模式】行为型-状态模式

在变幻的时光中&#xff0c;状态如诗篇般细腻流转。 文章目录 一、可调节的灯光二、状态模式三、状态模式的核心组件四、运用状态模式五、状态模式的应用场景六、小结推荐阅读 一、可调节的灯光 场景假设&#xff1a;我们有一个电灯&#xff0c;它可以被打开和关闭。用户可以…

Nvidia jetson Orin/Nano + 智能座舱摄像头实现车载AI视觉

智能座舱系统包括DMS&#xff08;Driver Monitor System&#xff09;驾驶员疲劳监测系统和OMS乘员监测系统&#xff0c;通过在车内安装摄像头感知驾驶员和乘客的行为以及车内状况&#xff1a;DMS摄像头能够实现驾驶员疲劳监测、驾驶员注意力监测、危险驾驶行为监测以及驾驶员身…

【python】python基于tkinter的学生成绩管理系统(源码+数据文件)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

如何做到高级Kotlin强化实战?(一)

高级Kotlin强化实战&#xff08;一&#xff09; 第一章 Kotlin 入门教程1.Kotlin 入门介绍2.Kotlin 与 Java 比较 第一章 Kotlin 入门教程 1.Kotlin 入门介绍 Kotlin 概述 Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言。它主要是 JetBrains 开发团队所开发出来的编程…

打靶记录——靶机medium_socnet

靶机下载地址 https://www.vulnhub.com/entry/boredhackerblog-social-network,454/ 打靶过程 由于靶机和我的Kali都处于同一个网段&#xff0c;所以使用arpscan二次发现技术来识别目标主机的IP地址 arpscan -l除了192.168.174.133&#xff0c;其他IP都是我VMware虚拟机正…

算法力扣刷题 二十六【459.重复的子字符串】

前言 字符串篇&#xff0c;继续。 记录 二十六【459.重复的子字符串】 一、题目阅读 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。示例…

在TkinterGUI界面显示WIFI网络(ESP32s3)摄像头画面

本实验结合了之前写过的两篇文章Python调用摄像头&#xff0c;实时显示视频在Tkinter界面以及ESP32 S3搭载OV2640摄像头释放热点&#xff08;AP&#xff09;工作模式–Arduino程序&#xff0c;当然如果手头有其他可以获得网络摄像头的URL即用于访问摄像头视频流的网络地址&…

浅谈Tomcat

文章目录 一、什么是Tomcat&#xff1f;二、Tomcat的下载安装三、使用tomcat访问资源 一、什么是Tomcat&#xff1f; Tomcat 就是一个 HTTP 服务器。 前面我们聊了HTTP服务器&#xff0c;像我们在网页输入URL&#xff0c;其实就是在给人家的HTTP服务器发送请求&#xff0c;既…

计算机网络之数据通信原理(中)

上节内容传送口&#xff1a;数据通信原理基础 1.数据传输方式 1.1并行传输 并行传输: 字符编码的各个比特同时传输 特点&#xff1a; 一个比特时间内可传输一个字符&#xff0c;传输速度快&#xff0c;每个比特传输要求一个单独的信道支持&#xff0c;通信成本高&#xf…

红黑树插入删除流程(流程图)

红黑树插入删除流程&#xff08;流程图&#xff09; 红黑树性质 左根右(二叉树&#xff09;根叶黑&#xff08;根节点是黑色的&#xff09;不红红&#xff08;不存在相邻两个红色节点&#xff09;黑路同&#xff08;对于每个节点&#xff0c;从该节点出发到任一空叶节点所经过…

收银系统源码-千呼新零售【全场景收银】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

intellij idea中使用R语言plot画图无图像问题

1、在intellij idea中使用R语言plot函数时&#xff0c;会遇到各种各样的问题&#xff0c;会出现图片不显示问题&#xff0c; 可以看到&#xff0c;目前我电脑r语言版本为4.2.1&#xff0c;输入下面代码&#xff1a; # # 安装包 # install.packages(ggplot2) # library(ggplot2…

理解MySQL核心技术:外键的概念作用和应用实例

引言 在数据库管理系统&#xff08;DBMS&#xff09;中&#xff0c;外键&#xff08;Foreign Key&#xff09;是维持数据一致性和实现数据完整性的重要工具。本文将详细介绍MySQL外键的基本概念、作用&#xff0c;以及相关的操作指南和应用实例&#xff0c;帮助读者掌握并灵活…

实现了Map接口的HashMap

HashMap 底层主要由以下几个部分组成&#xff1a; 数组 (Node<K,V>[] table): 这是一个数组&#xff0c;存储的是链表的头节点。默认大小为 16。链表 (Linked List): 当发生哈希冲突时&#xff0c;即不同的键具有相同的哈希值&#xff0c;HashMap 使用链表来解决冲突。链…

2024年06月CCF-GESP编程能力等级认证Scratch图形化编程一级真题解析

本文收录于《Scratch等级认证CCF-GESP图形化真题解析》专栏,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 一、单选题(每题 3 分,共 30 分) 第1题 小杨父母带他到某培训机构给他报名参加 CCF 组织的 GESP 认证考试的第 1级,那他可以选择的认证语言有几种?( ) A、…

C++ | Leetcode C++题解之第204题计数质数

题目&#xff1a; 题解&#xff1a; class Solution { public:int countPrimes(int n) {vector<int> primes;vector<int> isPrime(n, 1);for (int i 2; i < n; i) {if (isPrime[i]) {primes.push_back(i);}for (int j 0; j < primes.size() && i …