文先生的博客 求职,坐标深圳。(wenfh2020@126.com)

软件性能检测--火焰图 🔥

2020-07-30

火焰图是 svg 格式的矢量图,基于 perf 软件性能分析工具。通过对软件在系统上的工作行为记录进行采样。并将数据进行图形化,从而获得比较直观的可视化数据矢量图。


1. 概述

1.1. perf

基于 Linux 平台的 perf 采样脚本(fg.sh),对指定进程进行采样,生成火焰图 perf.svg

图片来源:Linux Performance


1.2. 火焰图

perf 采集的数据,可以通过插件生成二维火焰图:

  • Y 轴是函数块叠加而成,有点像程序调试堆栈;
  • X 轴代表程序函数,在单位时间内被采样的密集度。函数块越长,说明采样越多,工作频率越高,耗性能越多。

通过图象,我们对自己写的代码工作效率一目了然,这样可以针对性优化源码性能。

火焰图


2. 安装 perf 和 FlameGraph

1
2
3
4
5
6
7
8
# centos
yum install perf
# ubuntu
# apt-get install linux-tools-$(uname -r) linux-tools-generic -y
cd /usr/local/src
git clone https://github.com/brendangregg/FlameGraph.git
ln -s /usr/local/src/FlameGraph/flamegraph.pl /usr/local/bin/flamegraph.pl
ln -s /usr/local/src/FlameGraph/stackcollapse-perf.pl /usr/local/bin/stackcollapse-perf.pl

3. on-cpu 火焰图

进程/线程正在运行使用 cpu 的数据。


3.1. 脚本

通过脚本可以抓取到对应的进程/线程的数据,并将数据转换为火焰图。

【注意】 脚本不能监控正在睡眠一直不工作的进程/线程,否则抓取数据失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh

work_path=$(dirname $0)
cd $work_path

if [ $# -lt 1 ]; then
    echo 'pls input pid!'
    exit 1
fi

[ -f perf_with_stack.data ] && rm -f perf_with_stack.data
perf record -g -o perf_with_stack.data -p $1 -- sleep 20
perf script -i perf_with_stack.data | stackcollapse-perf.pl | flamegraph.pl > perf.svg
  • 命令。
1
./fg.sh <pid>

3.2. 定位问题

3.2.1. 问题一

上图可以看到 vsnprintf 在优化前使用频率非常高,占 6.7%。

在源码中查找 vsnprintf,发现日志入口,对日志等级 level 的判断写在 log_raw 里面了,导致不需要存盘的日志数据,仍然执行了 vsnprintf 操作。后面将日志过滤判断放在 vsnprintf 前,重复进行测试,占 1.54%,性能比之前提高了 5 个百分点 —— good 😄!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 优化后的的代码。 */
bool Log::log_data(const char* file_name, int file_line, const char* func_name,
                   int level, const char* fmt, ...) {
    /* 根据日志等级,过滤不需要存盘的日志。 */
    if (level < LL_EMERG || level > LL_DEBUG || level > m_cur_level) {
        return false;
    }
    va_list ap;
    char msg[LOG_MAX_LEN] = {0};
    va_start(ap, fmt);
    vsnprintf(msg, sizeof(msg), fmt, ap);
    va_end(ap);
    return log_raw(file_name, file_line, func_name, level, msg);
}

3.2.2. 问题二

如果不是火焰图,你无法想象 std::list::size() 这个接口的时间复杂度竟然是 O(N) 😱。

参考:《[stl 源码分析] std::list::size 时间复杂度

火焰图问题二


4. off-cpu 火焰图

有时候进程/线程因为某些阻塞操作很慢,我们仍然可以像 on-cpu 那样将采集的慢操作数据可视化为火焰图。详细原理请参考:Off-CPU Analysis

  • 慢操作。

图片来源:Off-CPU Analysis

  • 脚本 offcpu.sh,perf 数据采集和转化火焰图。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/sh

work_path=$(dirname $0)
cd $work_path

if [ $# -lt 1 ]; then
    echo 'pls input pid!'
    exit 1
fi

# 采集了某个进程,10 秒数据。
perf record -e sched:sched_stat_sleep -e sched:sched_switch \
	-e sched:sched_process_exit -a -g -o perf.data -p $1 -- sleep 10

perf script -i perf.data | stackcollapse-perf.pl | \
	flamegraph.pl --countname=ms --colors=io \
	--title="off-cpu Time Flame Graph" > perf.svg
  • 脚本使用。
1
./offcpu.sh <pid>
  • off-cpu 火焰图。展示了程序写日志到磁盘的阻塞操作的可视化记录。

5. 参考


作者公众号
微信公众号,干货持续更新~