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
+
483
556
[source, bash]
484
557
----
485
558
# 查询所有内核插桩和跟踪点
@@ -656,6 +729,75 @@ bpftrace -e 't:net:netif_receive_skb { @[str(args->name)] = lhist(cpu, 0, 128, 1
656
729
bpftrace -e 'k:ieee80211_* { @[func] = count(); }'
657
730
----
658
731
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
+
659
801
660
802
- 1. `kprobe:inet_accept`
661
803
* **挂钩函数**: `inet_accept`
@@ -676,6 +818,65 @@ bpftrace -e 'k:ieee80211_* { @[func] = count(); }'
676
818
677
819
678
820
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
10000
span>
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
+
679
880
==== 如何利用内核跟踪点排查短时进程问题?
680
881
681
882
在排查系统 CPU 使用率高的问题时,我想你很可能遇到过这样的困惑:明明通过 top 命令发现系统的 CPU 使用率(特别是用户 CPU 使用率)特别高,但通过 ps、pidstat 等工具都找不出 CPU 使用率高的进程。这是什么原因导致的呢?
@@ -863,6 +1064,40 @@ image::eBPF/image-2025-02-12-11-46-24-356.png[]
863
1064
image::eBPF/image-2025-02-12-11-49-17-694.png[]
864
1065
865
1066
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
+ ----
866
1101
867
1102
868
1103
0 commit comments