运维工程师的 perf 入门实战

用 2 个可运行实验,学会性能分析的"工程思路"

"我怎么用 perf 把问题抓出来"*


一、为什么运维一定要会 perf?

线上慢的时候,常见对话是:

  • 「是不是网络慢?」
  • 「是不是磁盘不行?」
  • 「CPU 不是才 30% 吗?」
  • 「感觉哪里不对,但说不清」

👉 perf 的价值只有一句话:

把"感觉慢",变成"函数级证据"。


实验一:用 perf 看懂 CPU / IO / 网络问题

这是 perf 的地基实验,只解决一件事:

这台机器,到底慢在 CPU、IO 还是网络?


实验一整体设计(你先看懂)

我们用一个 Python 程序,人为制造三类问题

模块 人为制造的问题 perf 里能看到
CPU 纯计算死循环 Python 函数热点
IO 频繁写文件 + fsync write / fsync
网络 socket 收发 tcp_sendmsg / recv

程序结构很简单:

复制代码
main
 ├── cpu_task()        # CPU 热点
 ├── io_task()         # IO 阻塞
 └── network_task()    # 网络 syscall

实验一代码(直接可运行)

保存为 perf_demo.py

python 复制代码
import threading
import socket
import time
import os

FILE_PATH = "/tmp/perf_io_test.log"
SERVER_ADDR = ("127.0.0.1", 9000)

# ---------------- CPU 密集 ----------------
def cpu_task():
    x = 0
    while True:
        for i in range(10000):
            x += i * i

# ---------------- IO 密集 ----------------
def io_task():
    with open(FILE_PATH, "a") as f:
        while True:
            f.write("hello perf\n")
            f.flush()
            os.fsync(f.fileno())
            time.sleep(0.001)

# ---------------- 网络服务端 ----------------
def network_server():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(SERVER_ADDR)
    s.listen(5)
    while True:
        conn, _ = s.accept()
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)

# ---------------- 网络客户端 ----------------
def network_client():
    time.sleep(1)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(SERVER_ADDR)
    while True:
        s.sendall(b"x" * 1024)
        s.recv(1024)

if __name__ == "__main__":
    threading.Thread(target=cpu_task, daemon=True).start()
    threading.Thread(target=io_task, daemon=True).start()
    threading.Thread(target=network_server, daemon=True).start()
    threading.Thread(target=network_client, daemon=True).start()

    while True:
        time.sleep(10)

运行:

bash 复制代码
python3 perf_demo.py

第一步:top ------ 确认"值得 perf"

bash 复制代码
top

你会看到:

  • python3 占用明显 CPU
  • load 有波动

👉 这一步不是分析,只是确认:机器真的在忙


第二步:perf stat ------ 定性判断瓶颈

bash 复制代码
perf stat -p $(pgrep -f perf_demo.py) sleep 10

重点看这几个:

指标 怎么理解
cycles CPU 工作量
instructions 干了多少事
IPC 每周期干几条指令
cache-misses 是否 cache 问题

运维级判断法则:

  • IPC < 1 → IO / syscall / 阻塞多
  • cycles 高但 instructions 不高 → 内核调用多

👉 这是"方向判断",不是结论


第三步:perf top ------ 实时热点(最直观)

bash 复制代码
perf top -p $(pgrep -f perf_demo.py)

你大概率会看到:

复制代码
python3        cpu_task
libc.so        write
vmlinux        tcp_sendmsg
vmlinux        recvfrom

👉 到这一步你已经知道:

  • CPU 在算什么
  • IO 在干嘛
  • 网络是不是在内核里忙

第四步:perf record + report ------ 固化证据

bash 复制代码
perf record -F 99 -p $(pgrep -f perf_demo.py) -g -- sleep 30
perf report

看 report 的核心原则:

  • Children,不是 Self
  • 排名前几的,就是问题来源

你会看到三类热点:

现象 含义
cpu_task CPU 计算热点
write / fsync IO 阻塞
tcp_sendmsg 网络 syscall

第五步:火焰图(强烈推荐)

bash 复制代码
perf script > out.perf
stackcollapse-perf.pl out.perf > out.folded
flamegraph.pl out.folded > flame.svg

怎么看火焰图(记住 3 条):

  1. 横向越宽,时间越多
  2. 最上面是"真正在跑的代码"
  3. 内核在下面,应用在上面

你会清楚看到三座"山":

  • CPU 计算山
  • IO 写文件山
  • 网络 syscall 山

实验一你应该学会什么?

✅ 不再凭感觉判断瓶颈

✅ 知道 CPU / IO / 网络在 perf 里的"长相"

✅ 能给出一句像样的结论:

perf 显示主要时间消耗在 cpu_task 计算及 write/fsync 系统调用,属于 CPU + IO 混合型负载。


实验二:锁竞争 + 网络慢的进阶分析

这个实验解决 线上最难判断的两类问题

CPU 不高,但系统很慢


实验二·场景一:制造锁竞争

在原程序中加入锁竞争

python 复制代码
lock = threading.Lock()

def lock_task():
    while True:
        with lock:
            time.sleep(0.002)

for _ in range(4):
    threading.Thread(target=lock_task, daemon=True).start()

perf 里怎么看锁?

bash 复制代码
perf top -p $(pgrep -f perf_demo.py)

你会看到:

复制代码
__pthread_mutex_lock
futex_wait
schedule

再采样:

bash 复制代码
perf record -F 99 -p $(pgrep -f perf_demo.py) -g -- sleep 20
perf report

看到 futex = 锁竞争(90% 没跑)


火焰图里的锁竞争特征

复制代码
futex_wait
 └── __pthread_mutex_lock
     └── python threading

👉 山不高,但很宽

👉 时间花在"等锁"


实验二·场景二:tcpdump + perf 分析网络慢

先用 tcpdump 看"包有没有问题"

bash 复制代码
tcpdump -i lo -nn -tttt tcp port 9000

关注:

  • 是否重传
  • ACK 是否延迟
  • Window 是否变小

再用 perf 看"慢在谁身上"

bash 复制代码
perf record -e tcp:tcp_sendmsg,tcp:tcp_recvmsg,net:netif_receive_skb \
-a -g -- sleep 20
perf report

判断逻辑:

现象 结论
tcp_recvmsg 高 应用处理慢
netif_receive_skb 高 网络/软中断
futex + schedule 应用被锁住

运维工程师级联合结论示例

tcpdump 未发现明显重传,RTT 稳定;

perf 显示大量时间消耗在 tcp_recvmsg 与 futex 等待,说明网络本身正常,瓶颈在应用处理和锁竞争。


最重要的一段(请记住)

perf 不给答案,它给证据链

标准分析路径只有一条:

复制代码
top
 ↓
perf stat(定性)
 ↓
perf top(热点)
 ↓
perf record(证据)
 ↓
火焰图(全局)
 ↓
结合代码下结论

给小白运维的一句话总结

tcpdump 证明"包怎么走"

perf 证明"时间花在哪"

火焰图让争论闭嘴


相关推荐
雪可问春风14 小时前
docker环境部署
运维·docker·容器
lwx91485215 小时前
Linux-Shell算术运算
linux·运维·服务器
翻斗包菜15 小时前
PostgreSQL 日常维护完全指南:从基础操作到高级运维
运维·数据库·postgresql
somi715 小时前
ARM-驱动-02-Linux 内核开发环境搭建与编译
linux·运维·arm开发
海的透彻15 小时前
nginx启动进程对文件的权限掌控
运维·chrome·nginx
为何创造硅基生物15 小时前
ESP32S3的RGB屏幕漂移问题
网络
好运的阿财15 小时前
process 工具与子agent管理机制详解
网络·人工智能·python·程序人生·ai编程
路溪非溪16 小时前
Linux驱动开发中的常用接口总结(一)
linux·运维·驱动开发
fzb5QsS1p16 小时前
告别重复造轮子,Qt 快速开发脚手架
开发语言·qt·php
航Hang*16 小时前
第3章:Linux系统安全管理——第2节:部署代理服务
linux·运维·服务器·开发语言·笔记·系统安全