在 Docker 中运行 Linux 性能分析工具 perf

文章目录

perf 简介

性能调优基本原理

在了解具体的工具之前,我们首先应该问自己,性能分析要追踪和优化什么。我们都知道程序的运行会占用包括 CPU,内存,文件描述符,锁,磁盘,网络等等在内的各种操作系统资源。根据 Amdahl 定律,当其中的某一个或多个资源出现瓶颈的时候,我们需要找到程序中最耗费资源的地方,并对其优化。

那么我们可能需要做如下这些事情:

  • 对系统资源持续进行观测以及时发现哪些资源出现了瓶颈
  • 统计各个程序(进程),确定哪个或哪些进程占用了过多的资源
  • 分析问题进程,找出其占用过量资源的原因

很多工具都可以用来要想获取这些信息,但它们本质上都是从操作系统提供的观测源查询数据,Linux 中的观测源被称为 event ,是不同内核工具框架的统一接口,大致有如下几种:

  • Hardware Events: 基于 CPU 性能监视计数器 PMC
  • Software Events: 基于内核计数器的低级事件。例如,CPU 迁移、主次缺页异常等等
  • Kernel Tracepoint Events: 硬编码在内核中的静态内核级的检测点,即静态探针
  • User Statically-Defined Tracing (USDT): 这些是用户级程序和应用程序的静态跟踪点
  • Dynamic Tracing: 可以被放置在任何地方的动态探针。对于内核和用户级软件,分别使用 kprobes 和 uprobes 框架
  • Timed Profiling: 以指定频率收集的快照。这通常用于CPU使用情况分析,其工作原理是周期性的产生时钟中断事件

perf_events_map

而 perf 就是一个 Linux 系统中的性能分析工具,它可以利用 Hardware Events, Software Events, Tracepoint, Dynamic Tracing 来对应用程序进行性能分析,从开发者的角度来讲,它可以分析如下各种问题:

  • 为什么内核消耗 CPU 高, 代码的位置在哪里?
  • 什么代码引起了 CPU 2级缓存未命中?
  • CPU 是否消耗在内存 I/O 上?
  • 哪些代码分配内存,分配了多少?
  • 什么触发了 TCP 重传?
  • 某个内核函数是否正在被调用,调用频率有多少?
  • 线程释放 CPU 的原因?

安装和使用

由于和内核的紧密关系,perf 的安装需要与内核版本相匹配,一般来讲使用发行版自带的包管理器安装即可,注意不同发行版下的包名称:

  • Alpine: perf,v3.12 以上才可安装
  • Debian: linux-perf,注意 Debian 10 的软件源中默认只有 4.19 版本,若需 5.10 版本,可使用 buster-backports 源
  • Ubuntu: linux-tools-*,星号为内核版本号或 generic

如果确实无法用包管理器安装或版本不匹配,可以下载对应版本内核源码并解压,在 tools/perf 目录下自行编译。

安装好 perf 后,可以用 perf --helpman perf 查看相应的帮助信息,下面仅介绍使用 perf 对应用进程进行分析的基本流程。

perf_events_flow

首先使用 perf record -p <pid> -g 来跟踪指定进程,此时 perf 会在前台进行性能监测,并在当前目录生成 perf.data 文件,当需要终止监测时,按 C-c 等待 perf 退出。

数据生成完毕后,可使用 perf report 在命令行下查看,如果想要可视化分析,可以结合 FlameGraph 这款工具生成 SVG 火焰图,命令如下:

git clone --depth=1 https://github.com/BrendanGregg/FlameGraph
sudo perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

flamegraph

PS: perf timechart 本身也提供了导出 SVG 图片的功能,但需要 perf timechart record 来记录,而且输出的是进程运行过程中系统调度的情况,无法对程序的具体代码段进行性能分析。

Docker 中运行 perf

实际场景中应用可能运行在 Docker 容器中,这时我们可以指定 PID 命名空间另开一个容器,使目标容器中的进程对其可见,然后在新开的容器中使用 perf 对应用进程进行分析,命令如下: docker run -it --pid=container:<目标容器ID> --network=container:<目标容器ID> <perf容器名>

但由于 Docker 出于安全考虑对系统调用 perf_event_open 进行了限制,在执行 perf 的过程中可能出现如下 Permission Error:

perf_event_open(..., PERF_FLAG_FD_CLOEXEC) failed with unexpected error 1 (Operation not permitted)
perf_event_open(..., 0) failed unexpectedly with error 1 (Operation not permitted)
You may not have permission to collect stats.
Consider tweaking /proc/sys/kernel/perf_event_paranoid:
 -1 - Not paranoid at all
  0 - Disallow raw tracepoint access for unpriv
  1 - Disallow cpu events for unpriv
  2 - Disallow kernel profiling for unpriv

一般可以通过三种方式解决:

  • 查看宿主机 /proc/sys/kernel/perf_event_paranoid 的值,设为 -1,这样非特权用户也能调用 perf_event_open
  • docker run 时加上参数 --cap-add CAP_SYS_ADMIN--privileged,赋予容器特权
  • 下载一份 seccomp 默认配置文件 ,在其中给 perf_event_open 放行,保存为 custom.json,在 docker run 时加上参数 --security-opt seccomp=custom.json

在容器本身来源可靠的情况下,第二种方式应该是较为安全且方便的,下面就给出 Dockerfile 样例:

FROM alpine:latest
RUN sed -i.bak 's/dl-cdn.alpinelinux.org/mirrors.cloud.tencent.com/g' /etc/apk/repositories
RUN apk add --update bash vim git perf perl thttpd
RUN git clone --depth=1 https://github.com/BrendanGregg/FlameGraph
RUN echo 'perf record -g -p $1' >  record.sh && \
    echo 'perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > $1' > plot.sh && \
    chmod +x *.sh

ENTRYPOINT ["bash"]

为方便使用编写了一键运行脚本:

#!/bin/bash

set -x

target_container_id="$1"
version="$(uname -r)"
version="${version%%-*}"
version="${version%.*}"
tag="v${2-$version}"
if [[ $tag != "v5.4" && $tag != "v5.10" ]]; then
    tag="latest"
fi
image=${3-"perf"}

docker run \
    --cap-add CAP_SYS_ADMIN \
    --privileged \
    -ti \
    --rm \
    --pid=container:$target_container_id \
    --network=container:$target_container_id \
    $image:$tag

复制以上代码保存为 attach.sh,执行 attach.sh <目标容器ID> 就进入了 perf 容器,此时可以使用 ps 查看目标容器中的进程,记下 pid 后执行 record.sh <pid> 开始记录,记录完成后运行 plot.sh <图片名.svg> 生成火焰图。

导出图片一般可使用 docker cpdocker exec 或挂载 volume,为方便预览和复制文件,容器内置了轻量网页服务,执行 thttpd -p <端口号> 即可。由于脚本中没有设置端口转发,需要 docker inspect <目标容器ID> | grep IPAdress 查看目标容器的 IP,然后在浏览器中访问即可。若需要更灵活的操作,可不用以上脚本手动添加参数运行容器。

扩展阅读

perf 及 Linux 性能调优:

Docker 中使用 perf:

分析 .NET 应用:

评论正在加载中...如果评论较长时间无法加载,你可以 搜索对应的 issue 或者 新建一个 issue