安装pvtrace和Graphviz
1. 安装pvtrace
- $ mkdir -p ~/project1 && cd ~/project1
- $ wget http://www.mtjones.com/developerworks/pvtrace.zip
- $ unzip pvtrace.zip -d pvtrace
- $ cd pvtrace
- $ make
- $ sudo make install
- # 查看pvtrace相关文件
- $ ls -1 pvtrace
- instrument.c
- Makefile
- stack.c
- stack.h
- symbols.c
- symbols.h
- trace.c
2. 安装graphviz
- $ sudo yum install graphviz
测试
在完成软件安装之后,编写一个测试程序(test.c),并进行测试。具体流程,如下:
1. 编辑测试文件test.c
- $ cd ~/project1
- $ cat << EOF > test.c
- #include <stdio.h>
- #include <stdlib.h>
- void test1()
- {
- printf("in test1.\n");
- }
- void test2()
- {
- test1();
- printf("in test2.\n");
- }
- void test3()
- {
- test1();
- test2();
- printf("in test3.\n");
- }
- int main(int argc, char *argv[])
- {
- printf("Hello wolrd.\n");
- test1();
- test2();
- test3();
- return 0;
- }
- EOF
2. 编译测试程序
- $ gcc -g -finstrument-functions test.c ./pvtrace/instrument.c -o test
- 注意: 必须有`-g -finstrument-functions`选项,否则后续就采集不到信息了。
3. 执行程序,生成信息文件trace.txt
- $ ./test
4. 通过pvtrace、可执行文件及trace.txt,生成信息文件graph.dot
- $ pvtrace test
5. 通过dot工具将graph.dot,转为图像文件graph.png
- $ dot -Tpng graph.dot -o graph.png
6. 浏览生成的图片
最终生成的图形效果,如下:
为已有的项目生成函数调用图
可以通过以下步骤为已有的项目生成函数调用图:
1. 将pvtrace源代码中的instrument.c
文件拷贝到项目中;
2. 增加对instrument.c文件的编译
3. 修改编译
选项:增加-g -finstrument-functions
4. 修改连接
选项:将instrument.o
连接到可执行文件中
5. 执行你的程序
6. 用pvtrace及“你的可执行文件”处理trace.txt
7. 用dot生成函数调用关系图
以下是对redis-2.4.17版本的处理,然后生成redis-cli启动及一个set操作对应的函数调用关系图:
支持C++的扩展
目前pvtrace不支持C++代码,如果有人希望改进,一种可行的改进思路,如下:
1. 修改instrument.c文件,支持C++环境的编译;
2. 通过c++filt
工具处理解析到的函数名标签,解析出实际的函数名: 为了支持继承、多态及函数重载等,C++编译时对函数名进行了特殊处理;
3. 采用合理的编码方式,确保步骤2中生成的函数名满足dot的语法(C++是用整个函数原型等信息来生成的函数签名的,所以步骤2中用c++filt
翻译出来的是函数原型(包括名字空间等信息));
4. 增加函数调用先后顺序的标识。
目标建立 nginx的运行流程图
参考https://www.ibm.com/developerworks/cn/linux/l-graphvis/
参考《深入剖析nginx》
参考http://stackoverflow.com/questions/6344318/pure-javascript-graphviz-equivalent/14866384
参考http://graphviz.org/content/cluster
my_debug.h
- #ifndef MY_DEBUG_LENKY_H
- #define MY_DEBUG_LENKY_H
- #include <stdio.h>
- void enable_my_debug( void ) __attribute__((no_instrument_function));
- void disable_my_debug( void ) __attribute__((no_instrument_function));
- int get_my_debug_flag( void ) __attribute__((no_instrument_function));
- void set_my_debug_flag( int ) __attribute__((no_instrument_function));
- void main_constructor( void ) __attribute__((no_instrument_function,constructor));
- void main_destructor( void ) __attribute__((no_instrument_function,destructor));
- void __cyg_profile_func_enter( void *,void * ) __attribute__((no_instrument_function));
- void __cyg_profile_func_exit( void *,void * ) __attribute__((no_instrument_function));
- #ifndef MY_DEBUG_MAIN
- extern FILE *my_debug_fd;
- #else
- FILE *my_debug_fd;
- #endif
- #endif
my_debug.c
- #include <stdio.h>
- #include <stdlib.h>
- /* Function prototypes with attributes */
- void main_constructor( void )
- __attribute__ ((no_instrument_function, constructor));
- void main_destructor( void )
- __attribute__ ((no_instrument_function, destructor));
- void __cyg_profile_func_enter( void *, void * )
- __attribute__ ((no_instrument_function));
- void __cyg_profile_func_exit( void *, void * )
- __attribute__ ((no_instrument_function));
- static FILE *fp;
- void main_constructor( void )
- {
- fp = fopen( "/usr/local/nginx_sendfile/sbin/trace.txt", "w" );
- if (fp == NULL) exit(-1);
- }
- void main_deconstructor( void )
- {
- fclose( fp );
- }
- void __cyg_profile_func_enter( void *this, void *callsite )
- {
- fprintf(fp, "E%p\n", (int *)this);
- }
- void __cyg_profile_func_exit( void *this, void *callsite )
- {
- fprintf(fp, "X%p\n", (int *)this);
- }
把my_debug.c和my_debug.h放到nginx的src/core/目录下
./configure后修改ojbs/Makefile文件
- CFLAGS = -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -finstrument-functions
- ...
- 18 CORE_DEPS = src/core/nginx.h \
- 19 src/core/my_debug.h \
- .....
- 84 HTTP_DEPS = src/http/ngx_http.h \
- 85 src/core/my_debug.h \
- ....
- 105 objs/nginx: objs/src/core/nginx.o \
- 106 objs/src/core/my_debug.o \
- ......
- 216 $(LINK) -o objs/nginx \
- 217 objs/src/core/nginx.o \
- 218 objs/src/core/my_debug.o \
- ......
- 331 objs/src/core/my_debug.o: $(CORE_DEPS) src/core/my_debug.c
- 332 $(CC) -c $(CFLAGS) $(CORE_INCS) \
- 333 -o objs/src/core/my_debug.o \
- 334 src/core/my_debug.c
make&&make install后
启动nginx,生成
/usr/local/nginx_sendfile/sbin/trace.txt
文件类似
- [root@haoning sbin]# head trace.txt
- E0x403f88
- E0x41f5ac
- X0x41f5ac
- E0x403f88
- X0x403f88
- E0x410425
- E0x40ff83
- E0x40fa71
- X0x40fa71
- E0x40ad4b
使用https://www.ibm.com/developerworks/cn/linux/l-graphvis/中的pvtrace
修改代码
symbols.h 中的
- 13 #define MAX_FUNCTIONS 20000
- 14 #define MAX_FUNCTION_NAME 5000
否函数太多会报错
pvtrace nginx
会得到
graph.dot
dot -Tjpg graph.dot -o graph.jpg
生成jpg,
这个.dot也可以用
http://www.tuidaoba.com/html/graphviz/
生成svg的图
根据dot的格式生成svg图的js为
- <html>
- <head>
- <meta charset="utf-8">
- <title>Viz.js</title>
- </head>
- <body>
- <script type="text/vnd.graphviz" id="cluster">
- digraph G {
- subgraph cluster_0 {
- style=filled;
- color=lightgrey;
- node [style=filled,color=white];
- a0 -> a1 -> a2 -> a3;
- label = "process #1";
- }
- subgraph cluster_1 {
- node [style=filled];
- b0 -> b1 -> b2 -> b3;
- label = "process #2";
- color=blue
- }
- start -> a0;
- start -> b0;
- a1 -> b3;
- b2 -> a3;
- a3 -> a0;
- a3 -> end;
- b3 -> end;
- start [shape=Mdiamond];
- end [shape=Msquare];
- }
- </script>
- <script src="viz.js"></script>
- <script>
- function inspect(s) {
- return "<pre>" + s.replace(/</g, "<").replace(/>/g, ">").replace(/\"/g, """) + "</pre>"
- }
- function src(id) {
- return document.getElementById(id).innerHTML;
- }
- function example(id, format, engine) {
- var result;
- try {
- result = Viz(src(id), format, engine);
- if (format === "svg")
- return result;
- else
- return inspect(result);
- } catch(e) {
- return inspect(e.toString());
- }
- }
- document.body.innerHTML += "<h1>Cluster (svg output)</h1>";
- document.body.innerHTML += example("cluster", "svg");
- </script>
- </body>
- </html>
结果如图

附件中
graph.dot.jpg 下载后改成graph.dot用文本打开可以看到dot文件内容
nginx.conf前面要加上
master_process off;