性能文章>借助Xpocket中的perf插件 了解cpu热点函数的抓取原理>

借助Xpocket中的perf插件 了解cpu热点函数的抓取原理原创

3年前
7972317

本文使用了xpocket工具包的插件链接

xpocket地址: https://plugin.xpocket.perfma.com
perf插件地址: https://plug in.xpocket.perfma.com/plugin/57

cpu热点抓取原理,怎么才能知道是进程的哪一个函数消耗了cpu资源呢?目前gperftools,async-profile,perf 都针对不同的语言提供了抓取cpu热点函数的功能,他们抓取的原理都很类似,如果不依赖内核支持的话,简单来说就是在用户空间设置一个timer定时器,timer以一定的频率向进程发送信号,在信号处理函数中可以拿到进程正在执行的调用栈,将采集到这些调用栈统计分析一下,数量最多的那个及时占用cpu最高的热点函数

gperftools的实现代码在profiler.cc,profile-handler.cc, 中定时器回调函数处理流程调用栈如下图所示。

1.jpg

async-proflie的itimer引擎使用了跟gperftools一样的原理,代码更加简单和清晰

#include <sys/time.h>
#include "itimer.h"
#include "os.h"
#include "profiler.h"

long ITimer::_interval;

// SIGPROF信号处理函数,在函数中获取进程的当前调用栈并存储
void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
    Profiler::_instance.recordSample(ucontext, _interval, 0, NULL);
}

Error ITimer::start(Arguments& args) {
    if (args._interval < 0) {
        return Error("interval must be positive");
    }
    _interval = args._interval ? args._interval : DEFAULT_INTERVAL;

    OS::installSignalHandler(SIGPROF, signalHandler);

    long sec = _interval / 1000000000;
    long usec = (_interval % 1000000000) / 1000;
    struct itimerval tv = {{sec, usec}, {sec, usec}};
    setitimer(ITIMER_PROF, &tv, NULL);
    //设置timer timer 到期后向当前进程发送SIGPROF信号
    return Error::OK;
}

void ITimer::stop() {
    struct itimerval tv = {{0, 0}, {0, 0}};
    setitimer(ITIMER_PROF, &tv, NULL);
}

原理比较清楚了,但是async-profile默认的引擎和perf工具则是依赖perf_event来抓取热点的并没有使用timer.

perf是一个功能强大的性能统计和分析工具 https://perf.wiki.kernel.org/index.php/Tutorial

perf_event是perf相关的一个系统调用,由内核提供给进程使用功能强大,其中的抓取cpu热点分支相对于上述timer方式存在下面几个优点

  1. 由硬件和内核触发,更加精确 在最初版本中可以看到当前运行函数的调用栈由intel_pmu_handle_irq()触发,This handler is triggered by the local APIC
  2. 因为代码在内核中,抓取热点函数调用栈非常的高效,对应用程序性能几乎没有影响
  3. 不同于gperftools和async-profile需要再目标程序中加载额外代码, perf_event对于目标程序没有任何入侵性。
  4. perf_event可抓取的信息非常丰富,cpu热点只是其中之一

下面我们使用一个例子,首先构造一个消耗cpu的函数, 该例子为了测试调用栈的抓取情况,调用层次比较复杂一些

#include <pthread.h>

const int num = 2;


int fun2()
{
      while(1) {}
}
int fun1()
{
    fun2();
}



int fun4()
{
      while(1) {}
}
int fun3()
{
    fun4();
}


int fun5()
{
      while(1) {}
}


void *func(void* arg) {
	fun1();
	return ((void *)0);
}

void *func3(void* arg) {
	fun3();
	return ((void *)0);
}



int main(int argc, char* argv[]) {
	int i,j;

	pthread_t threads[num];
	//for ( i = 0; i < num; i++) {
		pthread_create(&threads[i], NULL, func, NULL);
		pthread_create(&threads[i], NULL, func3, NULL);

	//}

        fun5();
	for (i = 0; i < num; i++) {
		pthread_join(threads[i], NULL);
	}
	return 0;
}

编译运行改程序,使用xpocket工具中的perf插件抓取cpu热点:
2.jpg
上图抓取进程 19507的cpu热点共抓到25549条数据,我们可以通过script命令看一下抓取到的数据大概是什么样子的
3.jpg
就是抓取的一条一条调用栈
通过report命令对抓取到的调用栈进行下汇总和总结得出热点报告
4.jpg
可以看到调用栈和每个函数占用cpu百分比都清晰的展示了出来。

点赞收藏
小子z
请先登录,查看3条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

从 Linux 内核角度探秘 JDK MappedByteBuffer

从 Linux 内核角度探秘 JDK MappedByteBuffer

17
3