QEMU 自带的简易计时器 — profiler 的简介及代码分析

我们应该怎么分析QEMU代码中某段代码的性能呢?除了比较复杂的trace-event功能(我的博客中翻译过qemu tracing的文档),其实在QEMU自带有一个简单的profiler实现,它是一个简单的计时器封装。这篇博客主要介绍怎么在编译时开启、使用QEMU profiler,并说明怎么利用这个功能添加一个自己的计时器来分析QEMU中某段代码的性能。

1. 编译

我的代码是QEMU 2.12.0。要开启profiler功能,在编译前进行运行configure的时候,只要加入--enable-profiler选项就可以了,它会加入CONFIG_PROFILER这个宏定义。比如我用如下选项进行编译:

cd QEMU_SRC_PATH
./configure --prefix=/PATH/TO/QEMU_BIN_DIR --target-list=x86_64-softmmu --enable-profiler
make -j
make install

将其编译到QEMU后,无须改动启动参数,它是默认启用的,但是我们一般需要用QMP shell来查看它帮我们计时的信息。

2. 使用

在启动QEMU虚拟机的命令中,我们需要加入一个QMP socket,用于一会接入我们的QMP shell:

# -qmp后边接我们要创建的QMP socket文件的信息,这里我们将它创建在本目录(./qmp-sock文件)
/PATH/TO/QEMU_BIN_DIR/bin/qemu-system-x86_64 ... -qmp unix:./qmp-sock,server,nowait

在虚拟机启动后,用QEMU源码中自带的QMP shell脚本连接QMP socket:

# 这个脚本就是QEMU源码中的scripts/qmp/qmp-shell文件
# 加-H是为了以HMP命令进行交互,否则就得使用json格式,不方便 
/PATH/TO/QEMU_SRC/scripts/qmp/qmp-shell -H ./qmp-sock

之后出现以下欢迎界面:

Welcome to the HMP shell!
Connected to QEMU 2.12.0

(QEMU) 

我们在(QEMU)后面输入info profile就可以看到QEMU自带的两个计时器的数值,每次查看完这个数值,数值会清零。

Welcome to the HMP shell!
Connected to QEMU 2.12.0

(QEMU) info profile
async time  18524863312 (18.525)
qemu time   0 (0.000)

3. 实现

QEMU源码中,计时器功能的实现,主要与monitor.cinclude/qemu/timer.h这两个文件有关文件实现。而使用这个profiler进行计时的只有两个变量:一个是vl.cdev_time(即上面的async time);一个是cpus.ctcg_time(即上面的qemu time),由于我们使用的是KVM,没有用TCG,所以这个值应该一直为0。

首先,计时器变量都定义在monitor.c,就是和QMP实现相关的代码文件中:

// monitor.c
int64_t tcg_time;
int64_t dev_time;

查询计时器的代码也在这附近,可以看到,的确和我们刚才用info profile查询时打印的信息一致,而且打印后进行了清零操作:

// monitor.c
static void hmp_info_profile(Monitor *mon, const QDict *qdict)
{   
    monitor_printf(mon, "async time  %" PRId64 " (%0.3f)\n", 
                  dev_time, dev_time / (double)NANOSECONDS_PER_SECOND);
    monitor_printf(mon, "qemu time   %" PRId64 " (%0.3f)\n",
                  tcg_time, tcg_time / (double)NANOSECONDS_PER_SECOND);
    tcg_time = 0;
    dev_time = 0;
}

include/qemu/timer.h中实现了计时器相关的函数,所以实际进行计时的代码段会包含它,其中也有计时器变量的外部声明:

// include/qemu/timer.h
#ifdef CONFIG_PROFILER
static inline int64_t profile_getclock(void)
{
    return get_clock();
}

extern int64_t tcg_time;
extern int64_t dev_time;
#endif

我们可以以dev_time这个计时器为例看一下,开始和结束计时之间的代码就是被计时的代码,很自然的方式:

// vl.c
#include "qemu/timer.h"
...
static void main_loop(void)
{
#ifdef CONFIG_PROFILER
    int64_t ti;
#endif
    while (!main_loop_should_exit()) {
#ifdef CONFIG_PROFILER
        ti = profile_getclock();  //开始计时
#endif
        main_loop_wait(false); // 被计时的就是这行代码
#ifdef CONFIG_PROFILER
        dev_time += profile_getclock() - ti;  //结束计时并累加到计时器
#endif
    }    
}

4. 扩展应用

根据这种思路,我们也很容易添加自己的计时器,只要以下几步:

  1. include/qemu/timer.h进行计时器变量声明;
  2. monitor.c进行计时器变量定义,并在hmp_info_profile函数加入自己喜欢的打印格式,在打印后也可以选择将计时器变量清零;
  3. 在要计时的代码段所在文件中包含qemu/timer.h头文件;
  4. 最后,在要计时的代码段前后加入profile_getclock()函数获取时间并累加到我们新定义的计时器变量上就可以了。

[1] 我的wiki–关于QMP, https://github.com/zhangjaycee/real_tech/wiki/virtual_018#接入qemu-monitor

[2] QEMU Docs, https://qemu.weilnetz.de/doc/qemu-doc.html

Leave a Reply

Your email address will not be published. Required fields are marked *