在 CentOS 系统中,使用 perf 工具看不到函数名,只能看到一些 16 进制格式的函数地址。php
其实,只要你观察一下 perf 界面最下面的那一行,就会发现一个警告信息Failed to open /opt/bitnami/php/lib/php/extensions/opcache.so, continuing without symbolhtml
这说明,perf 找不到待分析进程依赖的库。固然,实际上这个案例中有不少依赖库都找不到,只不过,perf 工具自己只在最后一行显示警告信息java
这个问题,其实也是在分析 Docker 容器应用时,咱们常常碰到的一个问题,由于容器应用依赖的库都在镜像里面。linux
这种方法从原理上可行,可是我并不推荐,一方面是由于找出这些依赖库比较麻烦,更重要的是构建这些路径,会污染容器主机的环境。git
不过,这须要容器运行在特权模式下,但实际的应用程序往只以普通容器的方式运行。因此,容器内部通常没有权限执行 perf 分析。github
比方说,若是你在普通容器内部运行 perf record,你将会看到下面这个错误提示:docker
perf_4.9 record -a -g 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)
固然,其实你还能够经过配置 /proc/sys/kernel/perf_event_paranoid 许非特权用户执行 perf 事件分析。性能优化
好比对于第 05 讲的应用,你能够执行下面这个命令:bash
mkdir /tmp/foo $ PID=$(docker inspect --format {{.State.Pid}} phpfpm) $ bindfs /proc/$PID/root /tmp/foo $ perf report --symfs /tmp/foo # 使用完成后不要忘记解除绑定 $ umount /tmp/foo/
不过这里要注意,bindfs 这个工具须要你额外安装。bindfs 的基本功能是实现目录绑定(相似于mount --bind),这里须要你安装的是1.13.10 版本(这也是它的最新发布版)。app
若是你安装的是旧版本,你能够到 GitHub上面下载源码,而后编译安装。https://github.com/mpartel/bindfs
一、先运行 perf record -g -p < pid>,执行一下子(好比 15 秒)后,按 Ctrl+C 中止。
二、而后,把生成的 perf.data 文件,拷贝到容器里面来分析
docker cp perf.data phpfpm:/tmp $ docker exec -i -t phpfpm bash
三、接下来,在容器的 bash 中继续运行下面的命令,安装perf 并使用 perf report 查看报告:
cd /tmp/ $ apt-get update && apt-get install -y linux-tools linux-perf procps $ perf_4.9 report
首先是 perf 工具的版本问题。在最后一步中,咱们运行的工具是容器内部安装的版本 perf_4.9,而不是普通的perf 命令。这是由于, perf 命令其实是一个
软链接,会跟内核的版本进行匹配,但镜像里安装的 perf 版本跟虚拟机的内核版本有可能并不一致
另外,php-fpm 镜像是基于 Debian 系统的因此安装 perf 工具的命令,跟 Ubuntu 也并不完也并不彻底同样。好比, Ubuntu 上的安装方法是下面这样
apt-get install -y linux-tools-common linux-tools-generic linux-tools-$(uname -r))
而在 php-fpm 容器里,你应该执行下面的命令来安装安装 perf:
apt-get install -y linux-perf
当你按照前面这几种方法操做后,你就能够在容器内部看到 sqrsqrt 的堆栈
事实上,抛开咱们的案例来讲,即便是在非容器化的应用中,你也可能会碰到这个问题。假如你的应用程序在编译时
使用 strip 删除了 ELF 二进制文件的符号表,那么你一样也只能看到函数的地址。
像是 Java 这种经过 JVM 来运行的应用程序,运行堆栈用的都是 JVM 内置的函数和堆栈管理。因此,从系统层面你只能看到 JVM 的函数堆栈,
perf_events 实际上已经支持了 JIT,但还须要一个 /tmp/perf-PID.map 文件,来进行符号翻译。固然,开源项目 perf-map-agent 能够帮你生成这符号表
此外,为了生成所有调用栈,你还须要开启 JDK 的选项-XX:+PreserveFramePointer。由于这里涉及到大量的 Java 知识,我就再也不详细展开了。若是你的应用恰好基于 Java ,那么你能够参考 NETFLIX 的技术博客连接为
https://medium.com/netflix-techblogjava-in-flames-e763b3d32166
-g, --call-graph=<print_type,threshold[,print_limit],order,sort_key[,branch],value> Display call chains using type, min percent threshold, print limit, call order, sort key, optional branch and value. Note that ordering is not fixed so any parameter can be given in an arbitrary order. One exception is the print_limit which should be preceded by threshold. print_type can be either: - flat: single column, linear exposure of call chains. - graph: use a graph tree, displaying absolute overhead rates. (default) - fractal: like graph, but displays relative rates. Each branch of the tree is considered as a new profiled object. - folded: call chains are displayed in a line, separated by semicolons - none: disable call chain display. threshold is a percentage value which specifies a minimum percent to be included in the output call graph. Default is 0.5 (%). print_limit is only applied when stdio interface is used. It's to limit number of call graph entries in a single hist entry. Note that it needs to be given after threshold (but not necessarily consecutive). Default is 0 (unlimited). order can be either: - callee: callee based call graph. - caller: inverted caller based call graph. Default is 'caller' when --children is used, otherwise 'callee'. sort_key can be: - function: compare on functions (default) - address: compare on individual code addresses - srcline: compare on source filename and line number branch can be: - branch: include last branch information in callgraph when available. Usually more convenient to use --branch-history for this. value can be: - percent: diplay overhead percent (default) - period: display event period - count: display event count
经过这个说明能够看到,-g 选项等同于 --call-graph,它的参数是后面那些被逗号隔开的选项,意思分别是输出类型、最小阈值、输出限制、排序方法、排序关键词分支以及值的类型。
咱们能够看到,这里默认的参数是 graph,0.5,callcaller,function,percent,具体含义文档中都有详细讲解,这里我就再也不重复了。
perf report -g graph,0.3
它只在系统初始化时建立 init 进程,以后它就成了一个最低优先级的空闲任务。也就是说,当 CPU 上没有其余任务运行时,就会执行 swapper 。因此,你能够称它为“空闲任务”。
展开它的调用栈,你会看到, swapper 时钟事件都耗费在了 do_idle 上,也就是在执行空闲任务。
由于在多任务系统中,次数多的事件,不必定就是性能瓶颈。因此,只观察到一个大数值,并不能说明什么问题。具体有没有瓶颈,还须要你观测多个方面的多个标,
来交叉验证。这也是我在套路篇中不断强调的一点。
Self 是最后一列的符号(能够理解为函数)自己所占比例;
Children 是这个符号调用的其余符号(能够理解为子函数,包括直接和间接调用)占用的比例之和。
perf 这种动态追踪工具,会给系统带来必定的性能损失。
vmstat、pidstat 这些直接读取 proc 系统来获取指标的工具,不会带来性能损失。
书籍:《性能之巅:洞悉系统、企业与云计算》
网站:http://www.brendangregg.com/linuxperf.html