原创,装载请标明引用地址,欢迎拍砖
1. 背景:
嘉龙在bprofile函数级别性能测试中抱怨:没有针对每个函数消耗cpu时间的统计。于是据此我做了一点研究:
(1) bprofile实际上是取N个时间点进行抽样统计,如果第i个时间点运行时栈中有本函数,则认为函数被调用一次。因此,bprofile报告中虽然说是“函数调用次数”,但实际上是“函数占用cpu时间”
(2) 运行时监控某进程的问题:
如果不在编译时对源代码做静态分析,而是在程序已经跑起来一会运行时对其监控,需要考虑哪些问题呢?
A. 如何获取进程中用到的变量和函数:
通过分析进程的“符号表”(变量和函数的符号名),且需要仔细研究“名称修饰”,恢复符号在源代码的原本定义;
B. 如何在运行时追踪某“函数被调用”的情况呢:
B.1 需要观察“运行时栈”,当函数A调用函数B时,参数以及A函数地址会被压栈,因此如果可以知道“运行时栈”中返回地址在哪儿,可以反查“符号表”,知道A函数被调用了。但是如何知道函数B正在被调用呢?
B.2 通常只能用gdb启动进程,然后观察运行时的“运行时栈”情况,但如果进程已经起来了(并且不是用gdb起来的),需要解决:如何从外部获取一个进程虚拟地址空间的信息?就我的知识,答案是:不能!回过头来想,bprofile对程序进行函数级别性能测试也需要用cpuprofile.sh去启动的,而不是等程序已经启动起来了,再去统计$cpuprof.sh -r ./bin/crsui
结论:
——如果进程已经启动(不是用我们编写的性能测试工具启动的),此时无论如何也不能对其进行函数级别性能测试——因为无法获取该进程虚拟地址空间的信息
但很不幸,后来找到一个工具gstack可以观察运行时栈(gstack pid),但还不清楚其原理
——理论上,可以反编译可执行文件(ELF),对汇编代码进行插桩(函数调用入口和出口统计调用时间,累加到hash表),统计每个函数的调用时间;汇编代码中,函数调用采用的是相对地址/绝对地址(长jmp/短jmp),而非函数名,但或许可以从符号表中反查出其函数名(binutils里面工具c++filt)。
2. C/C++中运行时堆栈分析
gdb : 常规方法,使用gdb进行调试,然后观察“运行时栈”信息
gstack : 但如果一个进程已经启动,再要观察他的“运行时栈”信息,可以用gstack pid。举例:
写一个小程序,涉及“运行时栈”信息,如下test.cpp
#include<iostream> using namespace std; void fun(int i){ cout<<"fun("<<i<<")"<<endl; sleep(2); } int main(){ int i=2; while(true){ fun(i++); sleep(2); } }
编译并后台运行
g++ -o test test.cpp nohup ./test &
通过gstack pid可以观察到如下信息
[[email protected] stack_trace]$ gstack 11583 #0 0x000000302af8f172 in __nanosleep_nocancel () from /lib64/tls/libc.so.6 #1 0x000000302af8f010 in sleep () from /lib64/tls/libc.so.6 #2 0x0000000000400a9d in main () [[email protected] stack_trace]$ gstack 11583 #0 0x000000302af8f172 in __nanosleep_nocancel () from /lib64/tls/libc.so.6 #1 0x000000302af8f010 in sleep () from /lib64/tls/libc.so.6 #2 0x0000000000400a71 in fun(int) () #3 0x0000000000400a93 in main ()
通过在源代码中打印出运行时栈的方法,但需要保证<exeinfo.h>(今天时间太紧,没空深究),否则报错:
[[email protected] stack_trace]$ g++ -o backtrace backtrace.cpp backtrace.cpp:12:21: exeinfo.h: No such file or directory backtrace.cpp: In function `void stack_info()': backtrace.cpp:22: error: `backtrace' was not declared in this scope backtrace.cpp:25: error: `backtrace_symbols' was not declared in this scope backtrace.cpp:26: error: expected `;' before "for" backtrace.cpp:26: error: `i' was not declared in this scope backtrace.cpp:26: error: expected `;' before ')' token backtrace.cpp: In function `void fun4(int)': backtrace.cpp:34: error: `show_stack_info' was not declared in this scope
源码如下:
/** * @file backtrace.cpp * @author work([email protected]) * @date 2013/08/10 13:36:29 * @version 1.0 * @brief * int backtrace(void **buffer, int size); * char **backtrace_symbols(void* const* buffer, int size); * void backtrace_symbols_fd(void* const* buffer, int size, int fd); **/ #include <exeinfo.h> #include <stdio.h> #include <stdlib.h> #define MAX_LEN 256 void stack_info(){ void* buffer[MAX_LEN]; int method_num; char **stack_frames; method_num = backtrace(buffer, MAX_LEN); printf("%d method(s) returned", method_num); stack_frames = backtrace_symbols(buffer, method_num) for(int i=0; i<method_num; i++){ printf("%s\n",stack_frames[i]); } free(stack_frames); } void fun4(int a){ if(a>0) fun4(--a); else show_stack_info(); } static void fun3(int a){ fun4(--a); } void fun2(int a){ fun3(--a); } void fun1(int a){ fun2(--a); } int main(){ fun1(10); return 0; }
3. Java中运行时堆栈分析
public class StackTrace { static void printStackTrace() { Throwable th = new Throwable(); /* * An element in a stack trace, as returned by * Throwable.getStackTrace(). Each element represents a single stack * frame. All stack frames except for the one at the top of the stack * represent a method invocation. The frame at the top of the stack * represents the execution point at which the stack trace was * generated. Typically, this is the point at which the throwable * corresponding to the stack trace was created. */ StackTraceElement[] eles = th.getStackTrace(); if (eles != null) { for (int i = 0; i < eles.length; i++) { System.out.println(eles[i]); // System.out.println("File name: " + eles[i].getFileName() // + ", Line number: " + eles[i].getLineNumber() // + ", Class name: " + eles[i].getClassName() // + ", Method name: " + eles[i].getMethodName()); } } } static int inner() { printStackTrace(); System.out.println("inner"); return 2; } static void outer(double d) { System.out.println("outer: " + inner()); } public static void main(String[] args) { outer(3.3); } }