3. bpftrace 基础语法:探针类型、动作块、内置变量与过滤条件

bpftrace 的脚本语法简洁而强大,其核心结构遵循 探针 : 动作块 的模式。理解探针类型、内置变量和过滤条件是编写高效分析脚本的基础。本节将系统性地拆解这些核心概念,并通过实例和图表帮助你快速掌握。

bpftrace 脚本结构全景图 bpftrace 脚本结构全景图 探针 (Probe) tracepoint:syscalls:sys_enter_read 过滤条件 (Filter) /pid == 12345/ 动作块 (Action) { @start = nsecs; } 语法说明: 探针类型: 指定要挂载的内核事件点(如 tracepoint、kprobe、uprobe)。 过滤条件: 使用 /条件表达式/ 对事件进行筛选,只有满足条件的事件才会执行动作块。 动作块: 用 {} 包裹的 bpftrace 命令序列,定义当探针触发时要执行的操作。 完整格式: 探针类型:探针名称 /过滤条件/ { 动作块 }

3.1 探针类型

探针是 bpftrace 的“传感器”,决定了脚本挂载到内核或用户空间的哪个位置。选择合适的探针类型是精确测量的第一步。以下是常用的探针类型及其适用场景。

探针类型 前缀 典型用途 开销
tracepoint tracepoint: 跟踪系统调用、调度、网络等稳定内核事件 低
kprobe/kretprobe kprobe: / kretprobe: 动态跟踪内核函数入口和返回 中
uprobe/uretprobe uprobe: / uretprobe: 动态跟踪用户态函数(如 libc、应用代码) 中
software software: 跟踪软件事件(如 CPU 时钟、上下文切换) 低
hardware hardware: 跟踪硬件事件(如 CPU 周期、缓存未命中) 低
profile profile: 基于时间间隔的采样分析(如每秒 99 次) 低
interval interval: 定时输出报告(如每秒打印一次统计信息) 极低
最佳实践: 对于系统调用延迟分析,tracepoint:syscalls:sys_enter_* 和 tracepoint:syscalls:sys_exit_* 是首选。它们接口稳定,参数丰富,且开销相对较低。仅在 tracepoint 无法覆盖的场景下(如内核内部函数),才考虑使用 kprobe。

3.2 动作块与内置变量

动作块是脚本的“执行器”,它利用内置变量来捕获上下文信息。bpftrace 提供了丰富的内置变量,用于获取进程信息、时间戳、内核栈等关键数据。

核心内置变量速查表

变量名 类型 描述 示例值
pid 整数 当前触发事件的进程 ID 12345
tid 整数 当前触发事件的线程 ID 12345
comm 字符串 当前触发事件的进程名称 nginx
uid 整数 当前触发事件的用户 ID 1000
nsecs 整数 系统启动以来的纳秒时间戳 1234567890123456
kstack 字符串 内核调用栈(用于定位内核代码路径) sys_read+0x1a
ustack 字符串 用户态调用栈(用于定位应用代码路径) read+0x10
args 结构体 访问 tracepoint 或 kprobe 的参数(如 args->fd) —
retval 整数 kretprobe 或 tracepoint 的返回值 0 或 -1
延迟测量核心公式: 在系统调用入口探针中记录 @start[tid] = nsecs;,在出口探针中计算 @latency_us = (nsecs - @start[tid]) / 1000;。这是后续所有延迟分析的基础。
内置变量与动作块交互图 内置变量与动作块交互图 事件触发 sys_enter_read 动作块 { } 记录时间戳 内置变量 pid, comm, nsecs 动作块常用操作: 1. 赋值: @start[tid] = nsecs; (使用 map 存储每个线程的开始时间) 2. 打印: printf("PID: %d, COMM: %s\n", pid, comm); 3. 统计: @latency = hist(nsecs - @start[tid]); (生成直方图)

3.3 基本过滤条件

过滤条件允许你从海量事件中精准定位目标,避免无关数据干扰分析。过滤条件写在两个斜杠 / / 之间,支持常见的比较和逻辑运算符。

常用过滤模式

  • 按进程 ID 过滤: /pid == 12345/ — 只跟踪特定进程。
  • 按进程名过滤: /comm == "nginx"/ — 只跟踪名为 "nginx" 的进程。
  • 按用户 ID 过滤: /uid == 0/ — 只跟踪 root 用户的操作。
  • 按参数过滤: /args->fd == 3/ — 只跟踪文件描述符为 3 的系统调用。
  • 组合过滤: /pid == 12345 && comm == "curl"/ — 使用 && (与) 或 || (或) 组合条件。
性能警告: 过滤条件虽然强大,但复杂的字符串匹配(如 comm == "long-process-name")会增加开销。对于高频事件(如 sys_enter_read),建议优先使用 pid 或 uid 等整数过滤,它们效率更高。

3.4 综合示例:测量特定进程的 read 系统调用延迟

以下脚本综合运用了 tracepoint 探针、内置变量和过滤条件,用于测量 PID 为 12345 的进程的 read 系统调用延迟。

#!/usr/bin/env bpftrace

BEGIN
{
    printf("Tracing read() syscall latency for PID 12345... Hit Ctrl-C to end.\n");
}

tracepoint:syscalls:sys_enter_read
/pid == 12345/
{
    @start[tid] = nsecs;
}

tracepoint:syscalls:sys_exit_read
/pid == 12345/
{
    $delta_us = (nsecs - @start[tid]) / 1000;
    @latency_us = hist($delta_us);
    @total_count++;
    delete(@start[tid]);
}

END
{
    printf("\nRead syscall latency distribution (us):\n");
    print(@latency_us);
    printf("Total calls: %d\n", @total_count);
    clear(@latency_us);
    clear(@start);
}
脚本解读:
  • BEGIN 和 END 是特殊探针,分别在脚本启动和退出时执行一次,用于初始化和清理。
  • @start[tid] 是一个关联数组(map),以线程 ID 为键存储开始时间,确保多线程环境下数据隔离。
  • $delta_us 是临时变量,使用 $ 前缀声明,用于存储计算出的微秒级延迟。
  • hist() 函数自动生成对数直方图,是分析延迟分布的利器。
  • delete(@start[tid]) 用于清理已处理完的线程记录,防止 map 无限增长。