登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

Linux bpftrace profile怎样按函数聚合CPU样本

来源:17golang原创

时间:2026-11-15 16:51:31 353浏览 收藏

想用 Linux 上的 bpftrace 找 CPU 热点时,profile 探针适合做按固定时间间隔抽样:每个 CPU 到达采样点就记录当前调用栈,再把相同函数或调用栈的样本累加。按函数聚合的关键不是把采样次数当成精确 CPU 秒数,而是先选好采样频率、栈类型和过滤条件,再用样本数量比较热点。

官方地址:https://bpftrace.org/

最小可用思路是:用 profile:hz:99 触发采样,用 ustack() 或 kstack() 作为聚合键,再用 PID、线程名或进程名收窄范围。样本越多只表示在采样窗口内越常出现在采样点,不等同于一次精确的函数耗时测量。

先分清 profile 采样到底统计什么

bpftrace 官方语言文档把 profile 定义为定时采样探针。它可以写成 profile:hz:99、profile:ms:10 或 profile:s:1 等形式;profile 探针按每个 CPU 的指定间隔触发,底层使用 Linux 的 perf events。这里的 99 Hz 是采样节奏,不是目标函数每秒被调用 99 次。

表达式含义适合场景
profile:hz:99按频率触发采样全局热点观察
profile:ms:10每 10 毫秒触发直观控制采样周期
profile:s:1每秒触发低成本长时间观察
interval:s:10每 10 秒执行一次 interval 代码输出或退出控制

因此,按函数聚合更接近采样型 profiling:在固定窗口里,某函数出现在栈顶或调用栈中的次数越多,说明它更可能是 CPU 热点。短函数、睡眠间隙、符号缺失和采样频率都会影响观察结果。

用 ustack 或 kstack 建立函数聚合键

最小的用户态聚合写法,是把当前用户态调用栈作为 map 的 key,把每次采样计为 1。下面是静态示例;代码只表达写法,不声称已经在本机执行。

profile:hz:99
{
    // 把用户态调用栈作为聚合键,每次采样增加一个样本
    @[ustack()] = count();
}

如果更关心内核路径,可以把聚合键换成 kstack();如果只想先看进程级分布,则可以按 comm 聚合:

profile:hz:49
{
    // 先按进程名聚合,低频率用于快速找出可疑进程
    @[comm] = count();
}

ustack() 回答用户程序从哪里消耗了采样点,kstack() 回答内核路径从哪里消耗了采样点,而 comm 只回答哪个进程名更常出现在采样点。排查时通常先从 comm 找范围,再切换到用户态或内核态栈。

bpftrace profile按时间采样并以用户态或内核态调用栈聚合的静态结构图
图1:结构图说明 profile 采样点如何进入 ustack、kstack 或 comm 聚合 map;这是原创静态说明图,不是截图或运行证据。

用 PID 过滤,再观察目标进程的调用栈

全机采样很容易被后台服务和内核线程淹没。已知目标进程 PID 时,可以在 probe 后面加条件,只保留该进程的样本:

profile:hz:99 /pid == 1234/
{
    // 只统计目标 PID 的用户态栈,避免其他进程污染结果
    @[ustack()] = count();
}

如果要按线程观察,可以使用 tid 作为一层 key,再配合用户态栈:

profile:hz:49 /pid == 1234/
{
    // 第一层区分线程,第二层保留用户态调用栈
    @[tid, ustack()] = count();
}

PID 过滤只限制采样事件中的当前进程,不会自动把子进程纳入统计;线程很多时把 tid 放进 key 会增加 map 基数;输出出现地址或 unknown 也不代表 profile 没有触发。

采样频率和运行窗口怎么选

频率越高,单位时间内样本越多,但观测成本、map 更新压力和输出体积也会增大。可以按这个顺序收敛:先用 49 Hz 左右摸底,再用 PID 或进程过滤,只有需要观察短函数或瞬时尖峰时才提高频率并缩短窗口。

# 先确认 bpftrace 版本;发行版文档应与实际版本对应
bpftrace --version

# 运行示例脚本并以短窗口观察;按 Ctrl-C 结束并输出 map
# ./cpu-profile.bt 只是示例脚本路径,不是固定系统路径
sudo bpftrace ./cpu-profile.bt

不要把采样频率机械地换算成函数占用百分比。均匀采样时,样本占比可以作为 CPU 热点的近似比较;调度、阻塞、符号化失败、CPU 亲和性和多进程竞争都会改变解释。对比两次采样时,应保持频率、窗口、过滤条件和负载一致。

bpftrace采样频率、目标过滤、运行窗口与样本解释边界的静态关系图
图2:说明图展示采样频率、过滤条件、窗口长度与样本解释之间的关系;图中是方法示意,不代表某次真实输出。

读懂 map 输出和符号化限制

脚本结束时,bpftrace 会打印非空 map。对 @[ustack()] = count() 来说,每组调用栈后的数字是该栈被采到的次数;对 @[comm] = count() 来说,数字是进程名对应的采样次数。它们都应被称为样本数,而不是函数调用次数或精确 CPU 时间。

现象优先解释下一步
某个栈样本数高该栈在窗口更常出现保持条件重复或延长窗口
只看到地址或 unknown符号表、权限或栈展开不足检查符号包、构建选项和版本
map 项很多聚合键过细或窗口过大先按 comm、PID 收敛
用户态栈为空用户栈无法展开或过滤不匹配分别检查过滤与符号条件

如果需要控制输出频率,可以把观测和输出控制分开:profile 负责累加样本,interval 负责周期性打印或清理 map。不要在每个采样点直接打印文本,否则大量输出会改变被观测程序的行为。

一套可复用的排障顺序

  1. 确认探针触发:先用 comm 或简单计数确认 profile 事件能产生数据。
  2. 确认范围:检查 PID、线程和 CPU 范围,没有把后台任务误当目标。
  3. 匹配栈类型:用户态问题用 ustack,内核路径用 kstack。
  4. 处理符号:地址解析失败时先处理符号和权限,再讨论函数热点。
  5. 重复采样:用相同负载和参数重复采样,避免只依赖一次短窗口。

一个函数样本数高,可能是它本身慢,也可能是它被许多路径共同调用;需要结合调用栈上下文、输入负载和阻塞状态继续分析。

常见问题

profile 统计的是函数调用次数吗?

不是。它按时间点采样当前执行上下文,聚合的是样本出现次数。调用很多但执行极短的函数,未必比少量但执行较久的调用得到更多样本。

为什么 99 Hz 不是每秒恰好 99 个样本?

profile 按每个 CPU 的节奏触发,CPU 数、调度状态、目标过滤和系统能力都会影响实际可见样本;99 Hz 只是请求的采样频率。

ustack 和 kstack 能放在同一个 map key 里吗?

不建议直接混用。两者描述的地址空间和符号解析边界不同,分开聚合更容易解释;需要对照时分别输出,再按同一窗口比较。

什么时候应该换用 perf 或专用 profiler?

需要成熟调用图、长期采样、复杂用户态符号处理或跨平台分析时,应评估 perf、语言运行时 profiler 或持续观测方案;bpftrace 更适合快速提出清晰的内核或用户态观测问题。

总结

Linux bpftrace 的 profile 探针提供按时间抽样的 CPU 观察入口。以 ustack()、kstack() 或 comm 为聚合键,把样本集中到 map 中,再通过 PID、线程和运行窗口逐层收敛,就能快速找出值得继续分析的函数路径。记住样本数是近似信号,不是精确 CPU 时间;频率、符号、过滤条件和重复窗口决定结论是否可信。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>