在 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 就是一个 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 --help
或 man perf
查看相应的帮助信息,下面仅介绍使用 perf 对应用进程进行分析的基本流程。
首先使用 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
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 cp
和 docker exec
或挂载 volume,为方便预览和复制文件,容器内置了轻量网页服务,执行 thttpd -p <端口号>
即可。由于脚本中没有设置端口转发,需要 docker inspect <目标容器ID> | grep IPAdress
查看目标容器的 IP,然后在浏览器中访问即可。若需要更灵活的操作,可不用以上脚本手动添加参数运行容器。
扩展阅读
perf 及 Linux 性能调优:
- Linux perf examples,FlameGraph 作者 Brendan Gregg 本人撰写,非常全面
- Linux 性能调优系列中文博客,其中有两篇博文分别介绍 perf 的原理和使用
- perf 原理和實務
- 性能分析利器之perf浅析
- Why ‘perf’ needs to match the exact running Linux kernel version?
Docker 中使用 perf:
- Tutorial: Profiling Rust applications in Docker with perf 详细可操作的教程,调试目标为 Rust 程序
- running
perf
in docker & kubernetes
分析 .NET 应用:
评论正在加载中...如果评论较长时间无法加载,你可以 搜索对应的 issue 或者 新建一个 issue 。