@@ -480,6 +480,79 @@ image::eBPF/image-2025-02-11-17-30-37-703.png[]
480480
481481安装好 bpftrace 之后,你就可以执行 bpftrace -l 来查询内核插桩和跟踪点了
482482
483+ - bpftrace内置变量精选
484+
485+ |===
486+ |内置变量 |类型 |说明
487+ |pid |integer |进程 ID(内核 tgid)
488+ |tid |integer |线程 ID(内核 pid)
489+ |uid |integer |用户 ID
490+ |username |string |用户名称
491+ |nsecs |integer |时间戳,以纳秒为单位
492+ |elapsed |integer |时间戳,以纳秒为单位,从 bpfrace 初始化开始
493+ |cpu |integer |处理器 ID
494+ |comm |string |进程名称
495+ |kstack |string |内核栈踪迹
496+ |ustack |string |用户级栈踪迹
497+ |arg0, ..., argN |integer |某些探针类型的参数
498+ |args |struct |某些探针类型的参数
499+ |sarg0, ..., sargN |integer |某些探针类型的栈参数
500+ |retval |integer |某些探针类型的返回值
501+ |func |string |被跟踪函数的名称
502+ |probe |string |当前探针的完整名称
503+ |curtask |struct/integer |内核 task_struct(可以是 task_struct 或无符号 64 位整数,取决于类型信息的可用性)
504+ |cgroup |integer |当前进程的默认 cgroup v2 ID(用于与 cgroupid() 做比较)
505+ |$1, ..., $N |int, char * |bpfrace 程序的位置参数
506+ |===
507+
508+ - bpftrace内置函数精选
509+
510+ |===
511+ |函数 |说明
512+ |printf(char *fmt [, ...]) |格式化打印
513+ |time(char *fmt) |打印格式化的时间
514+ |join(char *arr[]) |打印字符串数组,用空格字符连接
515+ |str(char *s [, int len]) |返回来自指针 s 的字符串,有一个可选的长度限制
516+ |buf(void *d [, int length]) |返回十六进制字符串版本的数据指针
517+ |strncmp(char *s1, char *s2, int length) |限定长度比较两个字符串
518+ |sizeof(expression) |返回表达式或数据类型的大小
519+ |kstack([int limit]) |返回一个深度不超过限制帧的内核栈
520+ |ustack([int limit]) |返回一个深度不超过限制帧的用户栈
521+ |ksym(void *p) |解析内核地址并返回地址的字符串标识
522+ |usym(void *p) |解析用户空间地址并返回地址的字符串标识
523+ |kaddr(char *name) |将内核标识名称解析为一个地址
524+ |uaddr(char *name) |将用户空间的标识名称解析为一个地址
525+ |reg(char *name) |返回存储在已命名的寄存器中的值
526+ |ntop([int af,] int addr) |返回一个 IPv4/IPv6 地址的字符串表示
527+ |cgroupid(char *path) |返回给定路径(/sys/fs/cgroup/...)的 cgroup ID
528+ |system(char *fmt [, ...]) |执行 shell 命令
529+ |cat(char *filename) |打印文件的内容
530+ |signal(char[] sig \| u32 sig) |向当前任务发送信号(例如,SIGTERM)
531+ |override(u64 rc) |覆盖一个 kprobe 的返回值
532+ |exit() |退出 bpfrace
533+ |===
534+
535+ - bpftrace内置的map函数
536+
537+ map是BPF特殊的哈希表存储对象,有多种不同的用途。例如可以作为哈希表存储键/值对或者用于统计汇总,bpftrace为map的赋值和操作提供了内置函数,多数内置函数用来支持统计汇总map的。
538+
539+ |===
540+ |函数| 说明
541+ |count()| 计算出现的次数
542+ |sum(int n)|数值求和
543+ |min(int n)|记录最小值
544+ |avg(int n)|求平均值
545+ |max(int n) |记录最大值
546+ |stats(int n) |返回计数、平均值和总数
547+ |hist(int n) |打印数值的 2 的幂级直方图
548+ |lhist(int n, const int min, const int max, int step) |打印数值的线性直方图
549+ |delete(@m[key]) |删除 map 中指定的键 / 值对
550+ |print(@m [, top [, div]]) |打印 map,包括可选的限制(只输出最高的 top 个)和除数(将数值整除后再输出)
551+ |clear(@m) |删除 map 上的所有键
552+ |zero(@m) |将 map 的所有值设为零
553+ |===
554+
555+
483556[source, bash]
484557----
485558# 查询所有内核插桩和跟踪点
@@ -656,6 +729,75 @@ bpftrace -e 't:net:netif_receive_skb { @[str(args->name)] = lhist(cpu, 0, 128, 1
656729bpftrace -e 'k:ieee80211_* { @[func] = count(); }'
657730----
658731
732+ ===== 利用bpftrace跟踪CPU
733+
734+ [source, bash]
735+ ----
736+ #跟踪带有参数的新进程:
737+ bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'
738+ #按进程对系统调用计数:
739+ bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[pid, comm] = count(); }'
740+ #按系统调用的探针名对系统调用计数:
741+ bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }'
742+ #以 99Hz 的频率对运行中的进程名采样:
743+ bpftrace -e 'profile:hz:99 { @[comm] = count(); }'
744+ #以 49Hz 的频率按进程名称对用户栈和内核栈进行系统级别的采样:
745+ bpftrace -e 'profile:hz:49 { @[kstack, ustack, comm] = count(); }'
746+ #以 49Hz 对 PID 为 189 的用户级栈进行采样:
747+ bpftrace -e 'profile:hz:49 /pid == 189/ { @[ustack] = count(); }'
748+ #以 49Hz 对 PID 为 189 的用户级栈进行 5 帧的采样:
749+ bpftrace -e 'profile:hz:49 /pid == 189/ { @[ustack(5)] = count(); }'
750+ #对名为 “mysqld” 的进程,以 49Hz 对用户级栈采样:
751+ bpftrace -e 'profile:hz:49 /comm == "mysqld"/ { @[ustack] = count(); }'
752+ #对内核 CPU 调度器的 tracepoint 计数:
753+ bpftrace -e 'tracepoint:sched:* { @[probe] = count(); }'
754+ #统计上下文切换事件的 off-CPU 的内核栈:
755+ bpftrace -e 'tracepoint:sched:sched_switch { @[kstack] = count(); }'
756+ #统计以 “vfs_” 开头的内核函数调用:
757+ bpftrace -e 'kprobe:vfs_* { @[func] = count(); }'
758+ #通过 pthread_create() 跟踪新线程:
759+ bpftrace -e 'u:/lib/x86_64-linux-gnu/libpthread-2.27.so:pthread_create { printf("%s by %s (%d)\n", probe, comm, pid); }'
760+ ----
761+
762+
763+ ===== 利用bpftrace跟踪内存
764+
765+ [source, bash]
766+ ----
767+ #按用户栈和进程计算 libc malloc() 请求字节数的总和(高开销):
768+ bpftrace -e 'u:/lib/x86_64-linux-gnu/libc.so.6:malloc { @[ustack, comm] = sum(arg0); }'
769+ #按用户栈计算 PID 181 的 libc malloc() 请求字节数的总和(高开销):
770+ bpftrace -e 'u:/lib/x86_64-linux-gnu/libc.so.6:malloc /pid == 181/ { @[ustack] = sum(arg0); }'
771+ #将 PID 181 的 libc malloc() 请求字节数按用户栈生成 2 的幂级直方图(高开销):
772+ bpftrace -e 'u:/lib/x86_64-linux-gnu/libc.so.6:malloc /pid == 181/ { @[ustack] = hist(pow2(arg0)); }'
773+ #按内核栈踪迹对内核 kmem 缓存分配的字节数求和:
774+ bpftrace -e 't:kmem:kmem_cache_alloc { @[kstack] = sum(args->bytes_alloc); }'
775+ #按代码路径统计进程堆扩展(brk(2)):
776+ bpftrace -e 'tracepoint:syscalls:sys_enter_brk { @[ustack, comm] = count(); }'
777+ #按进程统计缺页故障:
778+ bpftrace -e 'software:page-fault:1 { @[comm, pid] = count(); }'
779+ #按用户级栈踪迹统计用户缺页故障:
780+ bpftrace -e 't:exceptions:page_fault_user { @[ustack, comm] = count(); }'
781+ #按 tracepoint 统计 vmscan 操作
782+ bpftrace -e 'tracepoint:vmscan:* { @[probe]++; }'
783+ #按进程统计交换
784+ bpftrace -e 'kprobe:swap_readpage { @[comm, pid] = count(); }'
785+ #统计页面迁移
786+ bpftrace -e 'tracepoint:migrate:mm_migrate_pages { @ = count(); }'
787+ # 跟踪内存压缩事件
788+ bpftrace -e 't:compaction:mm_compaction_begin { time(); }'
789+ #列出 libc 中的 USDT 探针
790+ bpftrace -l 'usdt:/lib/x86_64-linux-gnu/libc.so.6:*'
791+ #列出内核的 kmem tracepoint
792+ bpftrace -l 't:kmem:*'
793+ #列出所有内存子系统 (mm) 的 tracepoint
794+ bpftrace -l 't:*:mm_*'
795+ ----
796+
797+
798+
799+
800+
659801
660802- 1. `kprobe:inet_accept`
661803* **挂钩函数**: `inet_accept`
@@ -676,6 +818,65 @@ bpftrace -e 'k:ieee80211_* { @[func] = count(); }'
676818
677819
678820
821+ === PMCs (硬件事件)
822+
823+ PMCs(Performance Monitoring Counters)是性能检测计数器,可以解释CPU周期性能
824+
825+ .github地址: https://github.com/brendangregg/pmc-cloud-tools/tree/master[PMC]
826+
827+ .可分别在容器和虚拟机中执行
828+ [source, bash]
829+ ----
830+ serverA# ./pmcarch -p 4093 10
831+ K_CYCLES K_INSTR IPC BR_RETIRED BR_MISPRED BMR% LLCCREF LLCMISS LLC%
832+ 982412660 575706336 0.59 126424862460 2416880487 1.91 15724006692 10872315070 30.86
833+ 999621309 555043627 0.56 120449284756 2317302514 1.92 15378257714 11121882510 27.68
834+ 991146940 558145849 0.56 126350181501 2530383860 2.00 15965082710 11464682655 28.19
835+ 996314688 562276830 0.56 122215605985 2348638980 1.92 15558286345 10835594199 30.35
836+ 979890037 560268707 0.57 125609807909 2386085660 1.90 15828820588 11038597030 30.26
837+ ----
838+
839+ K_INSTR:指示处理器周期数
840+ K_INSTR:指示处理器上执行的指令数
841+ IPC:每周期指令执行次数
842+
843+ IPC越高说明执行效率越好,性能也越好,一般是1.0以上,这偏低只有1.59左右,观察后面可以得出结论,LLC也就是虚拟机最后一级的缓存(LLC)命中率只有30%左右,因此导致指令在访问主存时经常停滞。
844+
845+ 通常是如下原因导致的:
846+
847+ - 较小的LLC大小 (33MB对45MB)
848+ - CPU饱和度高会导致更多上下文切换,以及更多的代码路径之间的跳跃(包括用户和内核),从而增加了缓存压力
849+
850+ 研究完硬件事件PMCs可以看下软件事件,我们可以使用perf命令查看计算机系统的上下文切换率。
851+
852+ .每秒钟上下文切换的次数
853+ [source, bash]
854+ ----
855+ serverA# perf stat -e cs -a -I 1000
856+
857+ # time counts unit events
858+ 1.000411740 2,063,105 cs
859+ 2.000977435 2,065,354 cs
860+ 3.001537756 1,527,297 cs
861+ 4.002028407 515,509 cs
862+ 5.002538455 2,447,126 cs
863+ 6.003114251 2,021,182 cs
864+ 7.003665091 2,329,157 cs
865+ 8.004093520 1,740,898 cs
866+ 9.004533912 1,235,641 cs
867+ 10.005106500 2,340,443 cs
868+ ^C
869+ 10.513632795 1,496,555 cs
870+ ----
871+
872+ 如果上下文切换过多会导致性能下降
873+
874+ 如果需要进一步跟踪可以使用bcc工具中的 cpudist,cpuwalk,runqlen,runqslower,cpuunclaimed
875+
876+
877+
878+
879+
679880==== 如何利用内核跟踪点排查短时进程问题?
680881
681882在排查系统 CPU 使用率高的问题时,我想你很可能遇到过这样的困惑:明明通过 top 命令发现系统的 CPU 使用率(特别是用户 CPU 使用率)特别高,但通过 ps、pidstat 等工具都找不出 CPU 使用率高的进程。这是什么原因导致的呢?
@@ -863,6 +1064,40 @@ image::eBPF/image-2025-02-12-11-46-24-356.png[]
8631064image::eBPF/image-2025-02-12-11-49-17-694.png[]
8641065
8651066
1067+ === 以下工具部分来自 perf-tools
1068+
1069+ https://github.com/brendangregg/perf-tools/tree/master[perf-tools]
1070+
1071+ === funccount
1072+
1073+ funccount 是一个用于统计函数调用次数的工具,它使用 eBPF 技术来统计函数调用次数,并输出统计结果。
1074+
1075+
1076+ [source, bash]
1077+ ----
1078+ #对VFS内核调用进行计数
1079+ funcgraph 'vfs_*'
1080+ # 堆TCP内核调用计数
1081+ funccount 'tcp_*'
1082+ # 对每秒的TCP发送调用计数
1083+ funccount -i 1 'tcp_send*'
1084+ # 显示每秒块I/O事件的次数
1085+ funccount -i 1 't:block_*'
1086+ # 每秒libc的getaddrinfo 名字解析 执行次数
1087+ funccount -i 1 c:getaddrinfo
1088+ ----
1089+
1090+ === stackcount
1091+
1092+ [source, bash]
1093+ ----
1094+ # 对创建块I/O的栈踪迹进行计数
1095+ stackcount -i 1 't:block:block_rq_insert'
1096+ # 对发送IP包的栈踪迹进行计数带对应PID
1097+ stackcount -p ip_output
1098+ # 针对导致线程阻塞并切换到off-CPU的栈踪迹进行计数
1099+ stackcount t:sched:sched_switch
1100+ ----
8661101
8671102
8681103
0 commit comments