bpftrace profile 探针怎么按固定频率采样用户态堆栈
来源:17golang原创
时间:2026-11-05 15:24:15 227浏览 收藏
bpftrace 可以用 profile:hz:49 请求固定频率的 CPU 采样,在谓词中按 PID 过滤,再用 @stacks[ustack(20)] = count(); 聚合用户态堆栈。profile 在每个 CPU 上按配置触发;PID 过滤决定哪些采样进入统计,并不保证目标进程每秒一定获得 49 个样本。
官方地址:https://bpftrace.org/docs/release_024/language
最小脚本:采集十秒,只保留目标 PID 的用户栈
将下面内容保存为 user-stacks.bt。它使用 bpftrace 0.24 文档中的语法,适用于具备对应 BPF 与 perf 事件能力的 Linux 环境。示例未在本文执行,没有实际进程的堆栈输出或性能结论。
// user-stacks.bt:面向 bpftrace 0.24 文档语法。
profile:hz:49
/pid == $1/
{
// 同一用户态堆栈作为一个键,只在内核映射中累加样本数。
@stacks[ustack(20)] = count();
}
interval:s:10
{
// 约十秒后请求退出;由 bpftrace 默认退出行为打印聚合映射。
exit();
}
# 将 4242 替换为 bpftrace 所在 PID 命名空间中可见的目标进程号。 # 仅对有权诊断的进程使用;本例只采样,不启动或终止目标进程。 sudo bpftrace user-stacks.bt 4242
49 Hz 是这个示例选择的起始频率,不是对所有机器推荐的最佳值。ustack 的 20 表示堆栈帧数上限,不是采样次数;interval:s:10 只负责结束采集,不采集目标进程的堆栈。目标进程本身不会因为这个脚本的 exit 被结束。
数据来源:每个 CPU 的采样事件
profile 使用 Linux perf_events。可以按 hz 指定频率,也可以用时间单位指定间隔;这里统一使用 hz,避免把频率数字与毫秒间隔混写。请求固定频率不等于没有调度和事件处理偏差的精密计时器。
采样时 CPU 上正在运行的执行上下文决定当前 pid。目标若在休眠、等待或大部分时间没有被调度运行,过滤后样本会少;目标有多个线程分布在多个 CPU 上运行时,聚合到同一个进程的样本又可能多于单个 CPU 的频率数字。
因此不能拿 49 乘十秒直接要求输出一定有 490 个目标样本。这种采样主要帮助观察目标获得 CPU 执行期间常出现的用户态调用栈,不是完整的阻塞时间分解,也不是每次函数调用的精确追踪。

进入映射前,明确 PID 的含义
脚本中的 $1 是命令后的第一个位置参数,pid 是采样上下文的进程 ID。按 0.24 文档,默认 pid 对应 bpftrace 所在 PID 命名空间可见的编号。若在容器内外取得了不同的数字,直接比较就可能匹配不到目标;先统一观察和执行所在的命名空间。
pid 过滤会把目标进程各线程的匹配样本放到同一个统计范围;若需要区分线程,可把 tid 加到聚合键中,但这会改变统计维度、增加键数量。不要把进程名 comm 当成唯一身份,多个进程可能同名。
本文选择显式谓词,不依赖附加选项暗中替所有 probe 做过滤。执行前还应确认 PID 对应的目标身份与采样窗口内的生命周期;长时间保存旧 PID 再启动采样,可能已经不是原来的进程。
存储模型:堆栈为键,样本数为值
ustack 返回用户堆栈类型,适合作为 map 键;count 是聚合函数。匹配同一个堆栈键的样本被累加,避免每次 profile 触发都把整条栈 printf 到终端。数据规模主要取决于观察到的不同堆栈数量与配置限制,而不仅仅是总事件数。
| 元素 | 本例职责 | 不能解释为 |
|---|---|---|
| profile:hz:49 | 请求每 CPU 的采样频率 | 目标进程必定每秒 49 次 |
| pid == $1 | 筛选目标进程上下文 | 目标每个线程单独输出 |
| ustack(20) | 最多采集二十帧用户栈 | 二十次调用或二十毫秒 |
| count() | 同键样本聚合计数 | 函数调用次数或请求数 |
若两条调用链只在第二十帧以后才不同,有限深度可能让它们在这里呈现相同前缀;增大深度会带来更多采集与存储成本。应先判断目前深度是否足以定位问题,再调整,不把最大深度当成默认目标。
查询与输出:退出时查看聚合结果
bpftrace 默认在程序退出时打印已填充的映射,所以本例无需再写 END 打印一次。interval 到时请求退出,随后正常收尾;退出和输出并非严格实时边界。如果手动中断,也应使用工具支持的正常退出方式,让收尾有机会完成。
读结果时先找样本较多的堆栈,再看是否与目标正在处理的负载相符。某条栈占已采集样本的比例,只是这一轮、这一 PID 与配置下的样本占比,不能直接写成整机 CPU 使用率,也不能单凭某函数出现在栈顶就认定其全部耗时。

异常处理:空统计和符号不全要分别看
没有样本时,先分辨是否根本未能附加,还是附加后 PID 没匹配、目标很快退出、窗口内目标没有足够的 CPU 活动。附加失败可能涉及内核支持、BPF/perf 访问策略或运行环境权限,应按管理员授权处理,不通过关闭安全限制来凑出结果。
若有地址但缺少符号,或堆栈明显很短,要分别考虑符号信息和栈展开条件。官方用户栈示例特别提及 frame pointers;目标的编译选项、优化和运行时都会影响可见性。缺失帧不能自动解释成“没有调用上层函数”,也不能把 JIT 语言的本机栈当成完整语言级栈。
采样频率、堆栈深度和映射容量都会影响开销。可以缩短窗口或降低频率来收敛诊断成本,但本文没有测得开销比例,不宣称这套配置对生产环境零影响。
清理与常见追问
本例不归档原始事件,只在内存映射中聚合,退出后释放本次追踪资源。若将输出重定向成文件,后续保存周期应由诊断流程决定,避免无限积累可能带有程序结构信息的结果。
用 interval 取栈能代替 profile 吗?
不能直接替代。interval 按间隔在一个 CPU 上触发,适合周期动作;profile 面向每 CPU 采样。把 interval 的当前上下文栈当作全进程 CPU 采样,会改变数据来源。
计数很大,就表示函数调用次数多吗?
这里统计的是采样命中次数。一次长时间运行的调用可能被多次采样,很多很短调用也可能没有命中,两者没有一一对应关系。
十秒窗口足够定位所有热点吗?
不保证。窗口需要覆盖有代表性的负载,低频任务可能未出现;先判断样本是否代表要研究的工作,再讨论延长窗口,而不是把缺少样本当成没有问题。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
385 收藏
-
134 收藏
-
264 收藏
-
171 收藏
-
371 收藏
-
269 收藏
-
445 收藏
-
193 收藏
-
119 收藏
-
208 收藏
-
423 收藏
-
484 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习