# 进程测试

# perf

:一个监控工具,能统计进程内各函数占用 CPU 时长的比例。

# top

perf top          # 监控当前主机执行的 CPU 事件(属于离散采样)
        -a        # 监控 CPU 的所有核。默认只监控第 0 个核
        -c <int>  # 监控 CPU 的第几个核
        -p <pid>  # 不监控 CPU 中所有进程的 event ,而是监控一个指定进程的 event

        -e <str>  # 采样的 event 类型,默认为 cpu-clock 。执行 perf list 查看可选的类型
        -F <int>  # 采样频率,每秒采样多少次 CPU 正在执行的 event 。默认为 4000 ,降低该值可以减少采样的开销,但可能遗漏 event
        -g        # 监控 event 的调用链

        -n        # 显示每个 event 采样的 samples 数量
        -v        # 显示详细内容,比如 Shared Object 的文件路径、Symbol 的内存地址
  • 例:

    [root@CentOS ~]# perf top -n
    Samples: 167K of event 'cpu-clock', 4000 Hz, Event count (approx.): 29827031201 lost: 0/0 drop: 0/0
    Overhead       Samples  Shared Object                                      Symbol
      3.53%          4206   [kernel]                                           [k] _raw_spin_unlock_irqrestore
      1.99%          2369   [kernel]                                           [k] __do_softirq
      1.85%          2206   libcoreclr.so                                      [.] 0x000000000030f332
      0.70%           836   perf                                               [.] rb_next
    ...
    
    • 默认会在一个窗口中显示监控结果,可按方向键上下选择 event ,按回车键进入子菜单。
    • 第一行表示累计采样了 167K 个 samples ,event 类型为 cpu-clock ,采样频率为 4000 Hz 。Event count 表示这段时间产生的 event 总数,只有部分被采样了。
    • 从第三行开始,每行表示一种名称的 event 。各列的含义如下:
      • Overhead :该 event 被采样的数量在全部样本中的比例,可用于估算该 event 占用 CPU 时长的比例。
      • Samples :该 event 被采样的数量。
      • Shared Object :产生该 event 的对象名称,通常是内核、动态链接库名、进程名。
      • Symbol :该 event 的名称,通常是正在执行的函数名称,或者是内存地址。前缀 [.] 表示其内存地址位于用户空间,[k] 表示位于内核空间。
  • 例:

    perf top -F 1000 -e block:block_rq_insert -n -a   # 监控块设备的 request queue 中新增的操作请求,横轴表示请求数
    perf top -F 1000 -e context-switches -gn -p <pid> # 监控进程的 context-switches 次数
    

# record

perf record [command] # 执行一条命令,并监控其中各个 event 的 CPU 使用率
        -o <file>     # --output ,将监控数据保存到一个文件中,默认为 $PWD/perf.data
        -- sleep 10   # 最多监控 10 秒。默认会一直监控,直到目标进程停止

perf report           # 读取 $PWD/perf.data 文件,显示 perf record 记录的信息
  • 例:
    [root@CentOS ~]# perf record -g -a -- sleep 10
    [ perf record: Woken up 2 times to write data ]
    [ perf record: Captured and wrote 1.940 MB perf.data (16068 samples) ]
    [root@CentOS ~]# perf report -n
    Samples: 16K of event 'cpu-clock', Event count (approx.): 4017000000
      Children      Self       Samples  Command        Shared Object       Symbol
    +   99.58%     0.00%             0  swapper        [kernel.kallsyms]   [k] start_cpu
    +   99.38%    99.38%         15969  swapper        [kernel.kallsyms]   [k] native_safe_halt
    +    3.46%     0.00%             0  swapper        [kernel.kallsyms]   [k] x86_64_start_kernel
         0.24%     0.00%             0  rcu_sched      [kernel.kallsyms]   [k] kthread
    ...
    
    • 如果某行的左侧显示 + ,则可按 Enter 键,显示子进程。
    • 每行表示一种名称的 event ,通常是函数名。
      • Self 列表示该函数被采样的数量在全部样本中的比例。
      • Children 列表示子孙函数被采样的数量,在该函数样本中的比例。

# stat

perf stat <command>   # 执行一条命令,并统计各种 event 的数量。等执行完命令,才显示统计结果
        -I 1000       # 每隔 1000ms 显示一次统计结果

perf stat -I 1000 -a  # 监控 CPU 所有核
  • 例:
    [root@CentOS ~]# perf stat uname
    Linux
    
      Performance counter stats for 'uname':
    
                  0.89 msec  task-clock                #    0.393 CPUs utilized
                      1      context-switches          #    0.001 M/sec
                      0      cpu-migrations            #    0.000 K/sec
                    165      page-faults               #    0.186 M/sec
        <not supported>      cycles
        <not supported>      instructions
        <not supported>      branches
        <not supported>      branch-misses
    
            0.002252426 seconds time elapsed
    
            0.000000000 seconds user
            0.001507000 seconds sys
    
    各字段的含义:
    task-clock (msec)     # 该命令使用 CPU 的毫秒数。备注的 CPUs utilized 表示使用了 CPU 的几个核
    context-switches      # 上下文切换的次数
    cache-misses          # CPU 在 cache 中找不到需要读取的数据的次数
    cpu-migrations        # 进程被迁移到其它 CPU 上运行的次数
    page-faults           # CPU 抛出 page fault 异常的次数
    cycles                # CPU 的时钟周期数。一条指令的执行可能需要多个 cycles
    instructions          # 指令数。Instructions/Cycles 的比值越大越好
    seconds time elapsed  # 执行该命令消耗的秒数
    

# 火焰图

  • perf report 命令会显示一个很长的列表,需要翻页。为了更高效率地分析 CPU 开销,Brendan Gregg 于 2011 年发明了火焰图(Flame Graph)。

  • 火焰图是根据 perf record 记录的数据,显示一张 svg 格式的图像。

    • 图像采用暖色调,形状带有很多尖刺,看起来像火焰。
    • 横轴表示 samples 数量。
      • 每格通常显示一个函数名。如果是匿名函数,则显示内存地址。
      • 一个函数占据横轴宽,说明抽样的 samples 数量多,即占用 CPU 时长的比例大,可能是性能瓶颈。
    • 纵轴表示调用链。
      • 火焰每升高一层,表示调用一层子函数。火焰越高,表示调用链越深。
      • 火焰的顶层是调用链的末端,即当前正在执行的函数。火焰的底层是调用链的源头。
      • 火焰的同一层,相同的函数会合并,不同的函数会按字母顺序从左到右排序。
    • svg 图像可以互动。
      • 鼠标点击图中某个函数,会放大图像,隐藏不在该函数调用链上的其它函数。
      • 按 Ctrl+F 可进行搜索,匹配搜索条件的函数会显示成紫色。
  • 例:生成火焰图

    perf record -F 100 -g -p <pid> -- sleep 10
    perf script -i perf.data > perf.script
    
    git clone https://github.com/brendangregg/FlameGraph.git
    FlameGraph/stackcollapse-perf.pl perf.script > perf.folded
    FlameGraph/flamegraph.pl perf.folded > perf.svg
    
  • 火焰图最初用于分析 CPU 开销,后来出现了其它类型的火焰图:

    • off-cpu 火焰图:横轴表示各函数阻塞(即未被 CPU 执行)的时长比例,适合分析 IO 密集型进程、分布式锁。
    • memory 火焰图:横轴表示各函数申请分配的内存量,适合分析内存密集型进程。

# strace

strace <command>          # 执行一条命令,并打印它调用的所有系统接口
       -c                 # 统计每个系统调用的次数、用时
       -e read,write,open # 只跟踪指定名称的系统调用
       -e trace=file      # 只跟踪指定类型的系统调用,比如 all、file、process、network、memory、signal
       -tt                # 在每行的开头显示时间
       -T                 # 在每行的末尾显示这次系统调用的耗时,比如<0.000007>

strace -p <pid>           # 跟踪一个进程
       -e trace=signal    # 只跟踪该进程收到的信号
  • 例:
    [root@CentOS ~]# strace -e trace=file echo hello
    execve("/usr/bin/echo", ["echo", "hello"], [/* 22 vars */]) = 0
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
    open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
    hello
    +++ exited with 0 +++
    

# time

time <command>    # 执行一条命令,并统计其占用的 CPU 时长
  • 例:
    [root@CentOS ~]# time sleep 1
    
    real    0m1.001s
    user    0m0.001s
    sys     0m0.000s
    
    • real time :该命令的持续时长。持续期间,不一定占用 CPU 。
    • user time :该命令占用的用户态 CPU 时长。
    • sys time :该命令占用的内核态 CPU 时长。
    • 该命令可能创建多个进程,占用多个 CPU 核心,可计算出 平均每秒占用 CPU 核数 = ( user + sys ) / real

# timeout

timeout <duration> <command>  # 以子进程的形式执行一条命令,并限制其运行时长,超时之后则终止命令
      -s <signal>             # --signal ,超时时发送的信号,默认为 SIGTERM
      -k <duration>           # --kill-after ,发送 signal 信号之后,最多等待 duration 时长,如果进程依然运行,则发送 SIGKILL 信号
      --preserve-status       # 如果超时,timeout 命令的退出码默认为 124 。启用该参数,则会让退出码与目标命令一致
  • duration 是浮点数,单位可以是 s、m、h、d ,默认为 s 即秒。
    • 如果设置为 0 ,则不限制。
  • 如果目标命令在 duration 之前结束,则 timeout 命令的退出码与该命令一致。
  • 例:
    timeout 3.5s ping localhost
    

# watch

watch <command>   # 周期性地执行一条命令,显示其 stdout 和 stderr
      -d          # 如果输出与上一次执行不同,则将差异部分高亮显示
      -e          # 如果命令的返回码不为 0 ,则停止执行
      -n 2        # 每隔几秒执行一次(默认是 2 秒)
      -t          # 不显示 watch 窗口的标题
  • 例:
    watch -d -n 1 ping localhost