尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

系统调用追踪实战:用strace、perf trace、bpftrace定位性能瓶颈

系统调用追踪实战:用strace、perf trace、bpftrace定位性能瓶颈 有一次线上网关服务的接口耗时突然从 40ms 涨到 800mstop 看下来 CPU 只有 25%内存、磁盘都正常GC 日志也没有异常。日志翻了一圈看不出所以然。后来我不猜了直接挂上系统调用追踪几分钟就锁定了问题线程大量卡在futex等待上顺着锁信息再查代码改完立刻恢复。这套排查方式就是我平时处理 Linux 性能问题的主要思路不要只盯着 CPU 和内存先搞清楚进程到底在内核态里等什么。今天把系统调用追踪和性能分析的实战方法完整写一遍从strace到perf trace再到bpftrace覆盖日常排查中最常用的手段。运维、后端、SRE 和做性能优化的同学都可以参考。1. 为什么盯系统调用是最快的定位切口1.1 应用卡住时CPU 数据很可能“骗人”大多数性能问题的第一反应是看 CPU。CPU 打满说明在“干活”但实际线上场景更常见的是CPU 不高接口却慢得离谱。这说明进程把时间消耗在了等待上而这种等待恰恰发生在内核里。比如线程在等锁、等网络包、等磁盘 IO、等另一个进程响应这些状态在 top 里看不出来。虽然可以看wa、si这些指标但无法告诉你是哪个进程、哪一类操作在等待。系统调用追踪能直接看到线程正在调用什么内核功能等的是read还是futex等了多少时间。1.2 系统调用是用户态和内核态之间唯一的口子用户程序要做文件读写、网络收发、内存分配、线程同步都必须通过系统调用进入内核。每一次系统调用都有清晰的“进入”和“返回”两个时间点这两个时间点之间的差值就是这次操作真正消耗的内核时间。这句话值得多念几遍只要程序慢不是用户态在算就是内核态在等。用户态在算CPU 会高内核态在等CPU 往往不高。追踪系统调用等于给进程装了一个“门禁监控”每次进出内核都会留下记录。1.3 一步区分用户态忙和内核等待用strace或者perf trace都能看到系统调用耗时。如果追踪结果里绝大多数系统调用的耗时都在微秒级CPU 高那瓶颈大概率在用户态计算如果某些系统调用单次耗时几十毫秒甚至更多那就是内核等待。这个方向一旦确认后续排查范围就小多了。我经常跟同事说系统调用追踪不是万能的但它是从“糊涂”走向“明白”的第一个台阶。2. strace 的正确打开方式先统计再抠细节2.1 别一上来就全量输出网上很多教程让你直接执行strace -p PID然后看滚动输出。这对小脚本没问题但在生产环境这么做会直接让服务瞬间变慢。因为 strace 基于 ptrace 实现每进入一次系统调用进程都会被停下由 tracer 记录完再放行。高并发下全量输出基本等于自杀。我的习惯是先上统计模式strace -f -c -p 12345-f跟随子进程和线程-c只做汇总不输出每条调用。跑 10 到 30 秒按 Ctrl-C 结束strace 会打印类似这样的统计% time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- -------------- 48.22 36.173490 128390 2815 futex 22.13 16.601211 20340 816 epoll_wait 15.02 11.265790 13980 806 read ...这张表能立刻告诉你进程把时间花在哪个系统调用上了调用次数多不多错误多不多。比盯着一万行滚动日志高效太多。2.2 熟悉这几个筛选和输出参数确认了目标系统调用之后才需要看具体调用细节。常用组合strace -f -T -tt -e tracefutex -p 12345-T显示每个系统调用耗时-tt显示微秒级时间戳-e trace指定追踪范围可以填read,write、network、file、desc这类分组也可以填具体系统调用名。看文件相关操作时建议加-y这样输出的文件描述符会带上具体路径。很多隐蔽问题就是“明明打开了某个文件结果操作的是另一个路径”。2.3 追踪正在运行的进程要注意几个硬约束第一ptrace 同一时间只能有一个 tracer 附着。如果你挂着 strace再执行gdb -p会失败反之亦然。曾经有同事在排障现场先挂了 gdb我 strace 半天附不上去浪费了不少时间。第二容器内追踪进程经常报Operation not permitted。这不是没权限而是容器没有CAP_SYS_PTRACE能力或者宿主机开了kernel.yama.ptrace_scope限制。要么用--cap-addSYS_PTRACE重新启动容器要么在宿主机上用nsenter进入进程的 pid namespace 再追踪。第三长时间挂 strace 本身会改变程序行为尤其是同步 IO 密集型的程序。所以 strace 适合“短平快”地取证不适合长时间开着当监控。3. perf trace同样看系统调用开销更可控3.1 为什么还需要 perf tracestrace 的问题在于 ptrace 开销太大。如果你在流量高峰期必须做追踪又不想背锅可以先试perf trace。它内部走的是内核 tracepoint 和 perf_event 子系统不是 ptrace开销比 strace 小一个数量级而且基本都能拿到同样的系统调用参数。用法非常像 straceperf trace -p 12345它会滚动输出进程的系统调用并且自带耗时列。也可以指定只追踪某几个系统调用perf trace -e read,write -p 12345想快速汇总加-sperf trace -s -p 12345-s模式输出的是聚合统计和strace -c类似但采集过程对服务的影响更小特别适合“线上先看看趋势”的场景。3.2 什么时候选 perf trace什么时候选 strace我自己心里的选择标准大概是这样的场景推荐工具理由进程启动后想跟踪子进程全貌strace参数解析更强大-ff按进程拆分日志高流量服务需要临时观察perf trace开销低汇总快只需要系统调用耗时分布perf trace数据来自内核 tracepoint统计更稳要详细看某个 syscall 的返回值和 errnostrace输出更完整和代码对接更直接老内核或容器受限场景两个都试试哪个能跑用哪个strace 还有一个难以替代的点它的-e tracefile、-e tracenetwork这类高级筛选非常直观适合快速在人脑里建立排查框架。perf trace 更偏原始功能上反而朴素。3.3 常见失败perf_event_paranoid如果你在普通用户下执行 perf trace遇到You may not have permission to collect stats.大概率是kernel.perf_event_paranoid限制。可以临时调整sudo sysctl -w kernel.perf_event_paranoid-1生产环境建议不要永久放宽用完改回去。另外容器里同样有 capability 限制需要检查是否具备CAP_PERFMON或CAP_SYS_ADMIN。4. bpftrace 与 tracepoint把调用链和耗时分布看透4.1 tracepoint 是比 ptrace 更稳的观察点strace 让人犹豫的地方是它会“停下来记录”。而内核从很早的版本开始就提供了 tracepoint比如raw_syscalls:sys_enter和raw_syscalls:sys_exit分别在系统调用进入和退出时触发。bpftrace 就是基于 BPF 技术在这些 tracepoint 上挂载程序只在内核里做统计把结果通过 ring buffer 传出来。它不会逐个暂停进程因此可以做到生产级、低开销的观测。4.2 一条命令统计各进程的系统调用次数最常用的 bpftrace 入门命令是这个bpftrace -e tracepoint:raw_syscalls:sys_enter { [comm] count(); }含义每次有进程发起系统调用就在以comm进程名为 key 的计数器上加一。程序跑一段时间后 Ctrl-C会按次数排序打印[nginx]: 384920 [java]: 251234 [sshd]: 1822这条命令比strace -f -c干净得多不需要 attach不占 ptrace对目标进程几乎没有侵入。特别适合判断“到底哪个进程在疯狂发系统调用”。4.3 统计系统调用耗时分布先看次数还不够还得看“有多少次是快的有多少次是慢的”。用 sys_exit 可以算每次系统调用的耗时bpftrace -e tracepoint:raw_syscalls:sys_enter { start[tid] nsecs; } tracepoint:raw_syscalls:sys_exit /start[tid]/ { usecs hist((nsecs - start[tid]) / 1000); delete(start[tid]); }输出是一张直方图能很直白地看到耗时是否分层。比如大部分在 10us 以下但有一批在 100ms 以上那就值得继续查这批慢调用属于哪个进程、哪类操作。用 bpftrace 还有一个高级玩法用comm java这类条件先过滤只统计目标进程避免数据被无关进程污染。4.4 安装 bpftrace 的几个提醒Debian/Ubuntu 上通常一句apt install bpftrace就能装CentOS/Rocky 用dnf install bpftrace。但有两个点需要提前确认内核需要开启 BPF 相关选项绝大多数发行版默认都开了老内核没有 BTFBPF Type Format信息时bpftrace 可能报Failed to load BTF。这时候要么换新内核要么试试回退到旧版本 bpftrace。如果项目长期需要系统调用级监控我建议不要只在出问题时才装 bpftrace而是把它固化到日常巡检脚本里定时输出一份系统调用排行。很多瓶颈是“量变到质变”的提前看到趋势比事后抢救有价值得多。5. 一次真实案例syscall 统计帮我锁定锁竞争与等待时间5.1 场景回顾CPU 只有 25%但 RT 飙升那次压测环境比较干净Java 网关应用QPS 大概 1 万。现象是服务 RT 从 40ms 爬到 800ms进程 CPU 只有 25%内存、GC、磁盘都正常下游 Redis、数据库都没有明显变慢。按照常规思路CPU 不高就应该怀疑线程在等待。等什么网络连接池锁日志里没有超时慢查询也没有。这时候最合适的手段就是系统调用统计。5.2 用 strace 汇总切出嫌疑范围执行strace -f -c -p 12345跑 30 秒后摘要长这样简化过% time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- -------------- 74.12 121.4730 241000 504 futex 15.22 24.9430 30000 831 epoll_wait 6.30 10.3200 120 860 readfutex占了将近四分之三的耗时。futex是用户态锁进入内核等待时的系统调用看到它大量耗时基本可以断定线程在用户态锁上发生竞争。继续看 Java 线程栈果然大量线程 BLOCKED 在同一把锁上。这里有个容易误判的点看到futex次数多不一定就是问题。futex调用次数少但单次等待几十毫秒才是锁竞争如果次数多但每次几十微秒可能只是正常的线程协调。一定要看耗时占比而不是调用次数。5.3 顺着系统调用反查代码系统调用只能告诉你“问题出在 futex”但不知道具体是哪把业务锁。这时要结合 Java 的线程 dumpjstack 12345 /tmp/jstack.txt在线程 dump 里搜BLOCKED、waiting to lock能直接看到各个线程在等哪个对象。那次排查结果是某个内存缓存的读写锁粒度太大热点 key 一多所有线程全在锁上排队。改法是锁粒度细化 热点 key 拆分成多个分片。上线后 RT 立刻回落到 45ms 左右。这次改动的起点就是 30 秒strace -c统计整个定位时间不到二十分钟。5.4 这个案例教给我的排查顺序先别管业务复杂度先回答三个问题进程时间主要花在哪个系统调用上是单次调用慢还是调用次数太多这个系统调用背后对应什么资源竞争回答完这三问大部分性能毛刺都能定位到方向。剩下的是业务层代码细节工具只能帮你缩小范围不能替你理解业务。6. 流量大时追系统调用的三个隐蔽坑6.1 第一个坑strace 的输出可能被磁盘拖死不要用默认方式把 strace 输出重定向到磁盘文件尤其是-o /tmp/strace.log加上-ff多文件输出。高流量下每秒钟可能有几十万条记录日志文件瞬间膨胀磁盘 IO 反过来成为新的瓶颈甚至把服务拖挂。如果要保存记录先输出到内存盘或者用-tt但只追踪目标系统调用并且限制采集时长。我通常的做法是timeout 15 strace -f -c -p 1234515 秒后自动退出只拿统计结果。需要明细时再加-e trace目标调用并且控制并发量。6.2 第二个坑errno 不一定是错误系统调用返回-1时strace 会显示 errno。看到ENOENT第一反应是“文件不存在”但很多程序启动时会做大量路径探查比如依次尝试打开多个配置文件、加载动态库时扫描多个目录这些ENOENT是正常流程不代表有问题。比较典型的例子Java 进程启动时会调用openat尝试很多路径找不到就ENOENT。如果你只看错误数很容易误判成“程序疯狂报错”。正确的做法是结合调用名和耗时看错误数量和类型是参考不是结论。6.3 第三个坑工具本身让性能问题“转移”不管是 strace 还是 perf trace都存在观测效应。strace 因为 ptrace 机制开销尤其明显。在已经不稳的进程上全量追踪可能导致线程调度延迟放大RT 变得更夸张你看到的数据是“被污染后”的假象。bpftrace 虽然轻但如果你在raw_syscalls:sys_enter上挂了复杂程序同样会占用 CPU。生产环境追查时我的原则是先低开销统计再小范围采样最后才考虑全量输出。每一步都只增加“刚好能得出结论”的观测成本不贪多。还有一个容易被忽略的小问题不要在生产高峰期长时间 attach。ptrace 在 attach 和 detach 的瞬间会对所有线程产生一次全局停顿如果业务线程数很多那一瞬间可能导致大量请求超时。所以执行strace -p之前一定确认你接受这个风险。7. 最后的排查习惯建议工具会了还要有稳定习惯。我现在遇到性能问题默认顺序是top看 CPU 分布 →pidstat看线程态 → 系统调用统计看等待方向 → 业务日志/线程栈精确定位。系统调用追踪永远是中间那一步作用是快速建立假设而不是直接给结论。建议你在压测环境先把 strace、perf trace、bpftrace 三条命令练熟尤其是 bpftrace 的直方图输出。等真正出问题时你只需要几秒钟就能决定用哪个工具、跑多长时间、看哪些字段。这个熟练度比记住再多命令参数都管用。
返回列表