8000 docs:完善性能分析工具和方法论文档 · andrewbytecoder/linux-note@1d91c13 · GitHub
[go: up one dir, main page]

Skip to content < 8000 /span>

Commit 1d91c13

Browse files
author
241200050
committed
docs:完善性能分析工具和方法论文档
- 新增 bpftrace 内置变量、函数和 map 函数说明 - 添加多个实用的 bpftrace 示例 - 介绍 PMCs (硬件事件) 和相关工具 -完善 USE 方法和 RED 方法的描述 - 添加工作负载归纳和数据追踪部分内容 - 更新 sar命令的说明和示例 - 新增 pidstat命令的简要介绍
1 parent 92bab0f commit 1d91c13

File tree

4 files changed

+448
-124
lines changed

4 files changed

+448
-124
lines changed

eBPF/ebpf.adoc

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
656729
bpftrace -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[]
8631064
image::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

linux/linux-performance.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@ endif::rootpath[]
1818

1919

2020

21+
=== CPU
2122

23+
如果处理器每个CPU有连个超线程,当超过50%的使用率意味着超线程核心存在争夺,会降低性能。
24+
25+
可以使用 `lscpu` 或者
26+
27+
`cat /proc/cpuinfo` 查看CPU信息

0 commit comments

Comments
 (0)
0