一、使用perf工具时,看到的是16进制地址而不是函数名?
在 CentOS 系统中,使用 perf 工具看不到函数名,只能看到一些 16 进制格式的函数地址。
观察perf界面最下面一行,就会发现一个警告信息:
说明,perf找不到待分析进程依赖的库。
这个问题,其实也是在分析 Docker 容器应用时,容器应用依赖的库都在镜像里面。
1.1、解决方法
1.1.1、法一:
在容器外面构建相同路径的依赖库。这种方法从原理上可行,但是并不推荐,一方面是因为找出这些依赖库比较麻烦,更重要的是,构建这些路径,会污染容器主机的环境。
1.1.2、法二:
在容器内部运行 perf。不过,这需要容器运行在特权模式下,但实际的应用程序往往只以普通容器的方式运行。所以,容器内部一般没有权限执行 perf 分析。
比方说,在普通容器内部运行 perf record ,将会看到下面这个错误提示:
当然,其实还可以通过配置 /proc/sys/kernel/perf_event_paranoid
(比如改成 -1),来允许非特权用户执行 perf 事件分析。
为了安全起见,这种方法也不推荐
1.1.3、法三:
指定符号路径为容器文件系统的路径。
- 注意
bindfs
这个工具需要额外安装。bindfs 的基本功能是实现目录绑定(类似于 mount --bind),这里需要你安装的是 1.13.10 版本(这也是它的最新发布版)。
如果是旧版本,可以Github上下载源码,编译安装。
1.1.4、法四:
在容器外面把分析纪录保存下来,再去容器里查看结果。这样,库和符号的路径也就都对了。
如:
先运行perf record -g -p <pid>
,执行一会(比如15秒)后,按Ctrl + C
停止。
然后把生成的perf.data文件,拷贝到容器里面来分析
接下来,在容器的 bash 中继续运行下面的命令,安装 perf 并使用 perf report
查看报告
注意:
- perf工具的版本问题。在最后一步中,运行的工具是容器内部安装的版本 perf_4.9,而不是普通的 perf 命令。这是因为, perf 命令实际上是一个软连接,会跟内核的版本进行匹配,但镜像里安装的 perf 版本跟虚拟机的内核版本有可能并不一致。
- php-fpm 镜像是基于 Debian 系统的,所以安装 perf 工具的命令,跟 Ubuntu 也并不完全一样。比如, Ubuntu 上的安装方法是下面这样
在php-fpm容器里,执行下面命令来安装perf:
当按照前面几种方法操作后,就可以在容器内部看到sqrt堆栈
实际上,抛开案例,即使在非容器化应用中,也可能会碰到这个问题,假如,应用程序在编译时,使用strip删除了ELF二进制文件的符号表,那么同样也只能看到函数的地址。
现在的磁盘空间,其实已经足够大。保留这些符号,虽然会导致编译后的文件变大,当对整个磁盘空间来说已经不是什么大问题。所以为了调试方便,建议保留它们。
二、如何用perf工具分析Java程序?
像是 Java 这种通过 JVM 来运行的应用程序,运行堆栈用的都是 JVM 内置的函数和堆栈管理。所以,从系统层面你只能看到 JVM 的函数堆栈,而不能直接得到 Java 应用程序的堆栈。
perf_events 实际上已经支持了 JIT,但还需要一个 /tmp/perf-PID.map
文件,来进行符号翻译。当然,开源项目 perf-map-agent 可以帮你生成这个符号表。
此外,为了生成全部调用栈,你还需要开启 JDK 的选项 -XX:+PreserveFramePointer
。涉及到大量Java知识这里不展开说,如果你的应用刚好基于 Java ,那么你可以参考 Netflix 的技术博客 Java in Flames ,来查看详细的使用步骤。
三、为什么perf报告中,很多符号都是不显示调用栈?
perf report 是一个可视化展示 perf.data 的工具。在这个案例中,直接给出了最终结果,并没有详细介绍它的参数。
这个界面可以清楚看到,perf report 的输出中,只有 swapper 显示了调用栈,其他所有符号都不能查看堆栈情况,包括我案例中的 app 应用。
这种情况以前也遇到过,当你发现性能工具的输出无法理解时,应该怎么办呢?当然还是查工具的手册。比如,可以执行 man perf-report 命令,找到 -g 参数的说明:
通过这个说明可以看到,-g 选项等同于 --call-graph,它的参数是后面那些被逗号隔开的选项,意思分别是输出类型、最小阈值、输出限制、排序方法、排序关键词、分支以及值的类型。
这里默认的参数是 graph,0.5,caller,function,percent
现在再回过头来看我们的问题,堆栈显示不全,相关的参数当然就是最小阈值 threshold。通过手册中对 threshold 的说明,我们知道,当一个事件发生比例高于这个阈值时,它的调用栈才会显示出来。
threshold 的默认值为 0.5%,也就是说,事件比例超过 0.5% 时,调用栈才能被显示。再观察我们案例应用 app 的事件比例,只有 0.34%,低于 0.5%,所以看不到 app 的调用栈就很正常了。
这种情况下,只需要给 perf report 设置一个小于 0.34% 的阈值,就可以显示我们想看到的调用图了。比如执行下面的命令:
这样就可以得到下面这个新的输出界面,展开 app 后,就可以看到它的调用栈了。
四、怎么理解perf report报告
上图中,swapper 高达 99% 的比例。直觉来说,我们应该直接观察它才对,为什么没那么做呢?
其实,当你清楚了 swapper 的原理后,就很容易理解为什么可以忽略它了。
看到 swapper,你可能首先想到的是 SWAP 分区。实际上, swapper 跟 SWAP 没有任何关系,它只在系统初始化时创建 init 进程,之后,它就成了一个最低优先级的空闲任务。也就是说,当 CPU 上没有其他任务运行时,就会执行 swapper 。所以,可以称它为“空闲任务”。
回到我们的问题,在 perf report 的界面中,展开它的调用栈,你会看到, swapper 时钟事件都耗费在了 do_idle 上,也就是在执行空闲任务。
所以,分析案例时,直接忽略了前面这个 99% 的符号,转而分析后面只有 0.3% 的 app。其实从这里你也能理解,为什么我们一开始不先用 perf 分析。
因为在多任务系统中,次数多的事件,不一定就是性能瓶颈。所以,只观察到一个大数值,并不能说明什么问题。具体有没有瓶颈,还需要你观测多个方面的多个指标,来交叉验证。
关于 Children 和 Self 的含义,手册里其实有详细说明,还很友好地举了一个例子,来说明它们的百分比的计算方法。简单来说
- Self 是最后一列的符号(可以理解为函数)本身所占比例;
- Children 是这个符号调用的其他符号(可以理解为子函数,包括直接和间接调用)占用的比例之和。
很多性能工具确实会对系统性能有一定影响。就拿 perf 来说,它需要在内核中跟踪内核栈的各种事件,那么不可避免就会带来一定的性能损失。这一点,虽然对大部分应用来说,没有太大影响,但对特定的某些应用(比如那些对时钟周期特别敏感的应用),可能就是灾难了。
所以,使用性能工具时,确实应该考虑工具本身对系统性能的影响。而这种情况,就需要你了解这些工具的原理。比如,
-
perf
这种动态追踪工具,会给系统带来一定的性能损失。 -
vmstat、pidstat
这些直接读取 proc 文件系统来获取指标的工具,不会带来性能损失。
五、性能优化书籍和参考资料
Brendan Gregg写的《Systems Performance: Enterprise and the Cloud》中文版《性能之巅:洞悉系统、企业与云计算》
他个人网站http://www.brendangregg.com/特别是Linux Performance这个页面,包含了很多 Linux 性能优化的资料,如:
- Linux性能工具图谱
- 性能分析参考资料
- 性能优化的演讲视频
不过,这里很多内容会涉及到大量的内核知识,对初学者来说并不友好。但是,如果你想成为高手,辛苦和坚持都是不可避免的。所以,在查看这些资料时,不要一遇到不懂的就打退堂鼓。任何东西的第一遍学习有不懂的地方很正常,忍住恐惧别放弃,继续往后走,前面很多问题可能会一并解决掉,再看第二遍、第三遍就更轻松了。
抓住主线不动摇,先从最基本的原理开始,掌握性能分析的思路,然后再逐步深入,探究细节,不要试图一口吃成个大胖子。