bpftrace技术2-map变量和map函数

Map 的核心概念与用途

在 bpftrace 中,符号 @用于定义和操作 Map 变量(或称映射变量)。这是 bpftrace 实现高效数据聚合的核心机制,你可以把它理解为一个在内核中运行的、功能强大的迷你数据库

Map 的主要作用是在不同的事件探针(probe)之间存储、共享和聚合数据。当某个事件触发时,你可以将数据记录到 Map 中;当另一个相关事件触发时,再从中读取或更新数据。这使得实现复杂的追踪逻辑成为可能。

特性 说明
数据持久化​ Map 中的数据在多个探针事件之间保持存在,不像普通变量那样每次事件触发后就被重置。(全局变量)
键值对结构​ Map 使用键(key)来索引值(value),格式为 @map_name[key] = value。键可以是单一值,也可以是多个值的组合(如 @a[pid, comm])。kv结构
自动输出​ 默认情况下,当 bpftrace 程序退出时(例如你按下 Ctrl-C),所有非空的 Map 内容会自动打印到屏幕上。

 Map 的常见操作与示例

操作/函数 说明 示例
赋值​ 直接给 Map 赋值。 @start_time[tid] = nsecs(记录线程的开始时间)
计数 (count())​ 统计事件发生的次数。 @syscall_count[comm] = count()(统计每个进程名的系统调用次数)
求和 (sum())​ 对数值进行累加。 @total_bytes[pid] = sum(args->ret)(累计每个进程读取的总字节数)
统计 (avg(), min(), max())​ 计算平均值、最小值、最大值。 @response_time = avg($latency)
直方图 (hist())​ 非常实用,生成2的幂次方的直方图,直观展示数据分布。 @latency_ns = hist(nsecs - @start[tid])(可视化读操作的延迟分布)
线性直方图 (lhist())​ 生成自定义区间的线性直方图。 @read_sizes = lhist(args->ret, 0, 10000, 1000)(统计读取大小的分布)
数据清理 (delete())​ 从 Map 中删除特定的键值对,防止内存无限增长。 delete(@start_time[tid])(在处理完一个事件后清理对应的开始时间)

 与其他变量的区别

变量类型 前缀 作用域与用途
Map 变量​ @ 全局。用于在探针之间持久化存储和聚合数据。
内置变量​ 无 只读。提供事件上下文信息,如 pid(进程ID)、comm(命令名)、retval(函数返回值)等。
暂存变量​ $ 局部临时。用于单次探针触发过程中的中间计算,例如 $duration = nsecs - @start[tid]。

map函数参考表

函数原型 核心作用与参数说明 典型应用场景
count()​ 计数。统计事件被触发的次数。无参数。 统计系统调用次数、函数调用次数等。
sum(int n)​ 求和。对参数 n的值进行累加。 计算总的字节读写量、总耗时等。
avg(int n)​ 求平均值。计算参数 n的平均值。 计算平均延迟、平均数据包大小等。
min(int n)​ 求最小值。记录参数 n的最小值。 追踪最小延迟、最小数据块大小。
max(int n)​ 求最大值。记录参数 n的最大值。 追踪最大延迟、最大数据块大小。
stats(int n)​ 统计摘要。返回参数 n的计数、平均值、总和、最小值、最大值。 获取一个指标的全面统计信息。
hist(int n)​ 对数直方图。按2的幂次方区间(如 [4-8), [8-16))展示参数 n的分布。 直观展示延迟、数据大小的分布情况,易于发现模式。
lhist(int n, int min, int max, int step)​ 线性直方图。在指定的线性区间(min到max,步长为step)内展示参数 n的分布。 当需要自定义固定区间进行分析时使用。
delete(@m[key])​ 删除键值对。从 Map @m中删除指定的 key及其对应的值。 清理临时数据,防止 Map 无限增长,常用于配对探针(如 kprobe/kretprobe)。
clear(@m)​ 清空 Map。清除 Map @m中的所有键值对。 在定时器(如 interval)中定期重置统计。
zero(@m)​ 归零 Map。将 Map @m中所有键的值重置为 0。 重置计数或求和等数据,但保留键的结构。

例子

count()- 统计系统调用次数

统计每个进程调用的系统调用次数

bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

输出

1
2
3
@count[bash]: 15
@count[sshd]: 28
@count[snmpd]: 102

  • @[comm]:以进程名 comm为键(key)。
  • count():每次事件触发,对应键的值加 1

sum(int n)- 计算读取的总字节数

累计所有进程通过 read 系统调用成功读取的字节数

bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ { @bytes = sum(args->ret); }'

输出

1
@bytes: 1048576

  • /args->ret > 0/:过滤器,只处理成功读取(返回值大于0)的情况。
  • sum(args->ret):对返回值(读取的字节数)进行累加

avg(int n)- 计算平均读取大小

计算每次 read系统调用成功读取的平均字节数。

bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ { @avg_size = avg(args->ret); }'

输出

1
@avg_size: 512

  • /args->ret > 0/:过滤器,只处理成功读取(返回值大于0)的情况。
  • avg(args->ret):对返回值(读取的字节数)求平均

stats(int n)- 获取完整的统计摘要

对 read系统调用的返回值进行全面的统计。

bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ { @s = stats(args->ret); }'

输出

1
@s: count 100, average 4096, total 409600, min 1, max 8192

  • /args->ret > 0/:过滤器,只处理成功读取(返回值大于0)的情况。
  • stats(args->ret):求调用次数,对返回值(读取的字节数)求平均,求总数,求最大最小值

hist(int n)- 分析读取字节数的对数分布

显示 read系统调用返回值的分布,区间按2的幂次方划分。

bpftrace -e 'tracepoint:syscalls:sys_exit_read { @bytes = hist(args->ret); }'

输出

1
2
3
4
5
6
@bytes:
[0, 1] 12 |@@@@@@@@@@@@@@@@@@@@ |
[2, 4) 18 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[4, 8) 0 | |
...
[128, 256) 1 |@

  • hist(args->ret):分为2^x ~2^y 大小的桶,统计每个桶的里面的数目个数

lhist(int n, int min, int max, int step)- 分析读取字节数的线性分布

使用线性直方图统计 read返回值,范围从0到2000,步长为200。

bpftrace -e 'tracepoint:syscalls:sys_exit_read { @bytes = lhist(args->ret, 0, 2000, 200); }'

输出

1
2
3
4
5
6
@bytes:
(..., 0) 0 | |
[0, 200) 66 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[200, 400) 2 |@ |
...
[2000, ...) 39 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |

  • hist(args->ret):分为0-200区间桶,统计每个桶的里面的数目个数

delete(@m[key])- 清理临时数据

在计算函数耗时后,删除用于存储开始时间的临时键,避免 Map 无限增长。

1
2
3
4
5
6
bpftrace -e 'kprobe:vfs_read 
{ @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/
{ $duration_ns = nsecs - @start[tid];
@us = hist($duration_ns);
delete(@start[tid]); }'

  • 此函数通常用于配对操作的探针(如 kprobe和 kretprobe),在操作完成后及时清理资源

clear(@m)和 zero(@m)- 重置 Map

每5秒打印并清空一次系统调用计数

1
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); } interval:s:5 { print(@); clear(@); }'
输出
1
2
3
4
5
6
7
8
@[Relay(89295)]: 3
@[mini_init]: 4
@[gmain]: 5
@[Relay(909)]: 6
@[Relay(893)]: 6
@[systemd-udevd]: 37
@[chronyd]: 40
@[bpftrace]: 118

  • clear(@m)会删除 Map 中的所有键值对。而 zero(@m)则将所有键的值重置为0,但保留键的结构
    1
    bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); } interval:s:5 { print(@); zero(@); }'
    输出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @[gmain]: 5
    @[Relay(893)]: 6
    @[Relay(909)]: 6
    @[Relay(89295)]: 9
    @[mini_init]: 12
    @[chronyd]: 44
    @[init]: 103
    @[bpftrace]: 114
    @[GnsPortTracker]: 242
    @[top]: 350
    @[grep]: 664
    @[sh]: 923
    @[Xwayland]: 1978
    @[ps]: 2778
    @[libuv-worker]: 4028
    @[ls]: 4256
    @[node]: 4722
    @[mini_init]: 0 <---------主要看这个,明显没有调用,但是结构被保留下来了
    @[Relay(109)]: 3
    @[Relay(909)]: 6
    @[Relay(89295)]: 6
    @[Relay(893)]: 6

map特点

  • 自动打印:默认情况下,当 bpftrace 程序终止时(例如用户按下 Ctrl-C),所有非空的 Map 变量会自动打印出来。
  • 过滤条件:使用 /<filter>/可以设置条件,只有满足条件时才会执行后面的动作,这能有效提升脚本效率和输出内容的针对性
  • 结合变量:Map 函数常与内置变量(如 comm, pid, nsecs)或临时变量(以 $开头)结合使用,以实现复杂的追踪逻辑