原创,装载请标明引用地址,欢迎拍砖
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可以观察到如下信息
[work@cq01-testing-sdcads-vir43.vm.baidu.com 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 () [work@cq01-testing-sdcads-vir43.vm.baidu.com 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>(今天时间太紧,没空深究),否则报错:
[work@cq01-testing-sdcads-vir43.vm.baidu.com 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(work@baidu.com)
* @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);
}
}