分类:
2012-09-11 11:38:30
原文地址:教你如何阅读源代码 作者:qingshanli1988
感谢linuxaid上这么精彩的文章!!!
分析一个源代码,一个有效的方法是:
1、阅读源代码的说明文档,比如本例中的README, 作者写的非常的详细,仔细读过之后,在阅读程序的时候往往能够从README文件中找到相应的说明,从而简化了源程序的阅读工作。
2、如果源代码有文档目录,一般为doc或者docs, 最好也在阅读源程序之前仔细阅读,因为这些文档同样起了很好的说明注释作用。
3、从makefile文件入手,分析源代码的层次结构,找出哪个是主程序,哪些是函数包。这对于快速把握程序结构有很大帮助。
4、从main函数入手,一步一步往下阅读,遇到可以猜测出意思来的简单的函数,可以跳过。但是一定要注意程序中使用的全局变量(如果是C程序),可以把关键的数据结构说明拷贝到一个文本编辑器中以便随时查找。
5、分析函数包(针对C程序),要注意哪些是全局函数,哪些是内部使用的函数,注意extern关键字。对于变量,也需要同样注意。先分析清楚内部函数,再来分析外部函数,因为内部函数肯定是在外部函数中被调用的。
6、需要说明的是数据结构的重要性:对于一个C程序来说,所有的函数都是在操作同一些数据,而由于没有较好的封装性,这些数据可能出现在程序的任何地方,被任何函数修改,所以一定要注意这些数据的定义和意义,也要注意是哪些函数在对它们进行操作,做了哪些改变。
7、在阅读程序的同时,最好能够把程序存入到cvs之类的版本控制器中去,在需要的时候可以对源代码做一些修改试验,因为动手修改是比仅仅是阅读要好得多的读程序的方法。在你修改运行程序的时候,可以从cvs中把原来的代码调出来与你改动的部分进行比较(diff命令), 可以看出一些源代码的优缺点并且能够实际的练习自己的编程技术。
8、阅读程序的同时,要注意一些小工具的使用,能够提高速度,比如vi中的查找功能,模式匹配查找,做标记,还有grep,find这两个最强大最常用的文本搜索工具的使用。
如何阅读源代码--工具篇
原创 02-01-21 09:10 15860p ariesram
在上一篇文章(<<如何阅读源代码>> (http://www.linuxaid.com.cn/developer/showdev.jsp?i=469))中, 我讲述了一些如何阅读GNU, Open Source源代码的原则,经验和技巧。上次曾经提到,有一些工具能够帮助我们更加快速,准确,有效的阅读源代码,掌握其结构。在这一篇文章中我将具体介绍几个工具,帮助我们阅读,分析源代码。
首先要介绍的工具叫做ctags. 这个工具在Unix下是一个常用的分析静态程序的工具,相信大家都用过。如果你对这个工具不熟悉,也不要紧。相信很多人都用过Windows系统下的开发工具,很多图形化界面的开发工具,诸如Visual C++, C++ Builder的IDE开发环境都提供了一种功能,就是在编辑器中可以准确的定位一个函数或者一个类的申明,或者实现,或者列出所有的在程序中调用该函数的地方。这种功能给程序员和阅读程序的人提供了很大的方便,不用在庞大的程序文本中到处搜寻一个字符串,只要轻轻的点一下鼠标就能准确的找到要找的东西。其实,Unix/Linux也有这样的工具,而且,继承了Unix程序小巧,精炼,功能强大,容易配合其它程序使用的特点,比Visual C++, C++ Builder的IDE环境更加方便实用,而且,还没有它们那么庞大。ctags结合vi,就是这么一个工具。
先来看看在Unix下,ctags的功能。我用的是HP UX。有这么一个小程序,是用来解释ctags的用法的。
$cat test.c
int i;
main()
{
f();
g();
f();
}
f()
{
i = h();
}
用命令产生一个tags文件。
$ctags test.c
缺省情况下,ctags生成的文件叫做tags. 来看一看它的内容。
$cat tags
Mtest test.c /^main()$/
f test.c /^f()$/
这个文件有三栏:
1、tag的名称,可以在稍后通过引用它来定位光标。
2、文件的名称。这个文件是tag所在的文件的名称。
3、搜索的方法。在我的系统上,ctags是用正则表达式的方式来搜索定位的。
如何使用这个tags文件呢?
我们以vi为例子来展示它的功能。vi是一个强大的文本编辑器,它的简洁的操作和强大的功能使它成为了Unix平台上的最流行的编辑器之一。ctags支持vi的功能。使用方法很简单。如果我们要定位main()函数,只要
$vi -t Mtest
vi 自动的打开了test.c文件,然后把光标定位到main()函数的开头处。在vi中,如果要使用其它的tag来定位别的函数,也只要使用:ta tag命令就可以了。比如在本例中,我们要定位f函数。那么,只要用 :ta f 光标就自动定位到f()函数的入口处。很简单吧?
在 Linux下,也可以找到ctags. 一般的Linux发行版都包括了这个工具。如果你的系统上没有ctags, 也可以到下载它。它的作者是Darren Hiebert
Linux下的ctags比Unix下的ctags功能更加强大,而且更加可以定制。Unix下的ctags(我的系统上是这样)只支持三种语言:C, Pascal, Fortran, 而Linux下的ctags支持的语言有:Assembler, AWK, BETA, Bourne shell, C/C++, COBOL, Eiffel, Fortran, Java, Lisp, Perl, Python, Scheme, Tcl. 而且Linux下的ctags支持的编辑器也很多,有:vi 和它的派生Vim, Vile, Lemmy, CRiSP, emacs, FTE (Folding Text Editor), NEdit (Nirvana Edit), TSE (The SemWare Editor), X2, Zeus.
好,现在就让我们来看一看ctags的强大功能以及它在阅读源代码的时候的用处吧。我将仍然以webalizer为例子,因为这个程序是在上一篇文章<<如何阅读源代码>>中使用过的,为了一贯性,也为了读者能够通过本文的阅读从而更加的了解这个程序和学到更多的经验技巧。考虑一下,当我们拿到一个C程序,我们如何能够快速的掌握它的结构呢?C程序是由一系列的函数,变量,宏,预编译指令组成的。而我们最为关心的是函数,和全局变量。那好,用ctags可以很方便的得到我们感兴趣的东西。
以webalizer的主程序webalizer.c为例子,我们可以用:
[webalizer-2.01-09]$ ctags -x --c-types=f webalizer.c
得到的结果如下
clear_month function 1614 webalizer.c void clear_month()
ctry_idx function 1738 webalizer.c u_long ctry_idx(char *str)
cur_time function 1695 webalizer.c char *cur_time()
from_hex function 1751 webalizer.c char from_hex(char c) /* convert hex to dec */
get_config function 1358 webalizer.c void get_config(char *fname)
get_domain function 1852 webalizer.c char *get_domain(char *str)
init_counters function 1627 webalizer.c void init_counters()
ispage function 1714 webalizer.c int ispage(char *str)
isurlchar function 1728 webalizer.c int isurlchar(char ch)
jdate function 1919 webalizer.c u_long jdate( int day, int month, int year )
main function 231 webalizer.c int main(int argc, char *argv[])
our_gzgets function 1873 webalizer.c char *our_gzgets(gzFile fp, char *buf, int size)
print_opts function 1657 webalizer.c void print_opts(char *pname)
print_version function 1670 webalizer.c void print_version()
save_opt function 1600 webalizer.c static char *save_opt(char *str)
srch_string function 1794 webalizer.c void srch_string(char *ptr)
unescape function 1763 webalizer.c char *unescape(char *str)
在输出中可以看到所有的在webalizer.c中的函数,出现的行号,和它们的申明。方便吧?(当然,仅仅这样是不能读通源代码的,还是需要上一篇文章中的原则和技巧才能实际的读懂源代码。这个只是一个辅助工具,能够让我们更加方便快速准确而已)。要看函数的原型也很简单:
[webalizer-2.01-09]$ ctags -x --c-types=p webalizer.c
得到的结果如下
clear_month prototype 87 webalizer.c void clear_month(); /* clear monthly stuff */
from_hex prototype 89 webalizer.c char from_hex(char); /* convert hex to dec */
get_config prototype 93 webalizer.c void get_config(char *); /* Read a config file */
get_domain prototype 96 webalizer.c char *get_domain(char *); /* return domain name */
isurlchar prototype 92 webalizer.c int isurlchar(char); /* valid URL char fnc. */
our_gzgets prototype 97 webalizer.c char *our_gzgets(gzFile, char *, int); /* our gzgets */
print_opts prototype 90 webalizer.c void print_opts(char *); /* print options */
print_version prototype 91 webalizer.c void print_version(); /* duhh... */
save_opt prototype 94 webalizer.c static char *save_opt(char *); /* save conf option */
srch_string prototype 95 webalizer.c void srch_string(char *); /* srch str analysis */
unescape prototype 88 webalizer.c char *unescape(char *); /* unescape URL's */
也可以看该程序中的变量:
[webalizer-2.01-09]$ ctags -x --c-types=v webalizer.c | more
得到的结果如下
all_agents variable 157 webalizer.c int all_agents = 0; /* List All U
ser Agents */
all_refs variable 156 webalizer.c int all_refs = 0; /* List All Ref
errers */
all_search variable 158 webalizer.c int all_search = 0; /* List All S
earch Strings */
all_sites variable 154 webalizer.c int all_sites = 0; /* List All si
tes (0=no) */
all_urls variable 155 webalizer.c int all_urls = 0; /* List All URL
's (0=no) */
all_users variable 159 webalizer.c int all_users = 0; /* List All Us
ernames */
blank_str variable 138 webalizer.c char *blank_str = ""; /* blank st
ring */
buffer variable 217 webalizer.c char buffer[BUFSIZE]; /* log file
record buffer */
check_dup variable 179 webalizer.c int check_dup=0; /* check for dup
flag */
conf_fname variable 135 webalizer.c char *conf_fname = NULL; /* name
of config file */
copyright variable 106 webalizer.c char *copyright = "Copyright 1997
-2001 by Bradford L. Barrett";
ctry_graph variable 117 webalizer.c int ctry_graph = 1; /* country gr
aph display */
cur_day variable 171 webalizer.c cur_day=0, cur_hour=0, /* trackin
g variables */
cur_hour variable 171 webalizer.c cur_day=0, cur_hour=0, /* trackin
g variables */
cur_min variable 172 webalizer.c cur_min=0, cur_sec=0;
cur_month variable 170 webalizer.c int cur_year=0, cur_month=0, /* y
由于内容太多,就不一一列在这里了。读者可以自己练习一下。
用同一个命令格式,可以看到更多的内容,用--c-types来指定。它们是:
c 类
d 宏定义
e 枚举
f 函数定义
g 枚举名称
m 类,结构和联合
n 名字空间
p 函数原型和申明
s 结构名称
t typedef
u 联合名称
v 变量申明
x 外部和引用变量申明
也可以不指定--c-types,来列举所有的类型。
也可以用这种命令格式来分析.h头文件,例如,列举在webalizer.h头文件中定义的所有的宏,
[webalizer-2.01-09]$ ctags -x --c-types=d webalizer.h | more
BUFSIZE macro 14 webalizer.h #define BUFSIZE 4096 /* Max buffe
r size for log record */
IDX_2C macro 5 webalizer.h #define IDX_2C(c1,c2) (((c1-'a'+1
)<<5)+(c2-'a'+1) )
IDX_3C macro 6 webalizer.h #define IDX_3C(c1,c2,c3) (((c1-'a
'+1)<<10)+((c2-'a'+1)<<5)+(c3-'a'+1) )
IDX_4C macro 7 webalizer.h #define IDX_4C(c1,c2,c3,c4) (((c1
-'a'+1)<<15)+((c2-'a'+1)<<10)+((c3-'a'+1)<<5)+(c4-'a'+1) )
IDX_ACCEPTED macro 81 webalizer.h #define IDX_ACCEPTED 5
IDX_BAD macro 93 webalizer.h #define IDX_BAD 17
IDX_BADGATEWAY macro 113 webalizer.h #define IDX_BADGATEWAY 37
IDX_BADHTTPVER macro 116 webalizer.h #define IDX_BADHTTPVER 40
IDX_CONFLICT macro 102 webalizer.h #define IDX_CONFLICT 26
IDX_CONTINUE macro 77 webalizer.h #define IDX_CONTINUE 1
IDX_CREATED macro 80 webalizer.h #define IDX_CREATED 4
IDX_EXPECTATIONFAILED macro 110 webalizer.h #define IDX_EXPECTATIONFAILED 34
(因为篇幅太长,未能列出全部,下面略去)
当然,ctags的功能还不止是这么多,更重要的功能是生成一个tags文件,从而能够在vi等编辑器中快速定位函数,类等源代码。ctags生成的缺省的文件名是tags, 也可以用 ctags -f 来指定一个文件名。不过,缺省的文件名tags也是vi的缺省tag文件名,一般不需要改变。在本例中,我们用
[webalizer-2.01-09]$ ctags *.c
来生成所有的.c文件的tags。当然,如果源代码是在很多个子目录下,也可以通过参数--recurse来自动的搜寻所有的在当前子目录下的*.c文件。看一看tags文件的内容。
[webalizer-2.01-09]$ head tags
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted/
!_TAG_PROGRAM_AUTHOR Darren Hiebert /darren@hiebert.com/
!_TAG_PROGRAM_NAME Exuberant Ctags //
!_TAG_PROGRAM_URL /official site/
!_TAG_PROGRAM_VERSION 4.0.3 //
BLACK output.c 118;" d file:
BLUE output.c 122;" d file:
CLK_TCK dns_resolv.c 71;" d file:
CLK_TCK hashtab.c 59;" d file:
可以看到,文件分为几栏,第一栏是tag的名字,比如函数put_unode,它的tag名字就是put_unode. 第二栏是文件名,是包含该tag的文件的名称,第三栏是用于ex命令中定位该函数(或者其他)的位置的.这个ex命令和在Unix下ctags产生的命令不同,不过也可以通过命令行来指定命令的类型。生成了tags文件之后,我们可以很方便的在多而且庞大的源代码文件快速准确的定位我们需要查找的信息了。还是以vi为例子,比如我们要定位main()函数,只要
[webalizer-2.01-09]$ vi -t main
vi就自动的打开webalizer.c文件,并且把光标定位到第231行的开始位置,这就是main()函数的入口位置。在vi编辑器中,如果要查找 put_unode()函数,只要:ta put_unode, vi就自动的打开文件hashtab.c, 并且把光标自动的定位到344行的开始位置,这就是put_unode的开始位置。再比如,我们要查找某一个变量um_htab的申明,只要:ta um_htab, 就能够很快的定位到hashtab.c文件的第88行。也可以通过:ta tag这个ex命令定位任何一个tag。需要注意的是,有了这个tags文件,我们并去需要打开一个特定的源代码文件来查找这个tag, vi 会自动的打开这个文件,非常的方便。当然,ctags是一个功能极其强大的工具,读者可以参考它的man page或者访问它的网站来发现它的更多的功能。
另外一个类似于ctags的工具是etags, 这是为编辑器emacs(相信很多人喜欢这个编辑器,它的功能太多太复杂了:-))专用的工具,功能上和ctags基本一样,不同的是,etags产生的缺省文件是TAGS, 这个文件被emacs默认为是tag文件。
还有一些工具,和ctags有着类似的功能。它们是
cxref ()
cxref这个工具能够为C源代码产生LaTeX, HTML, RTF 或者 SGML格式的交互引用的文档。但是目前它不支持C++, 而且它的作者似乎也不准备增加支持C++的功能。
cflow (http://www.opengroup.org/onlinepubs/7908799/xcu/cflow.html)
也是一个生成文档的工具,是一个Unix下的常用工具。
举一个例子,在我的HP UX平台上,有这么一个源代码
$>cat test.c
int i;
main()
{
f();
g();
f();
}
f()
{
i = h();
}
用cflow可以得到:
$>cflow test.c
1 main: int(),
2 f: int(),
3 h: <>
4 g: <>
列出了函数出现的文件名和行号,以及调用关系(用行缩进的方式表示出来)。
上面讲了一些静态的分析程序的工具,下面要讲的是一些动态的分析程序的工具。动态分析工具也是很重要的一种分析工具。因为当一个程序太大太复杂的时候,仅仅凭借人来读这个程序是很浪费时间的,而且效率也不高。采用了动态分析工具,能够快速准确有效的来分析一个程序,得到一些信息,比如,你的程序在哪里花费了很多时间,什么函数调用了什么函数,一个函数被调用了多少次,等等。这些信息能够帮助你知道你的程序的什么地方执行的速度比你想象的要慢,能够帮助你改写这些程序代码以便提高程序的运行速度。这些信息也能够告诉你,那些函数被调用的次数多于或者少于你预期的次数,从而帮助你定位错误和修正bug。 当然,这些信息是在你的程序运行的时候动态得到的,所以,如果你的程序的某些特性某些代码没有被执行到,那么凭借动态分析工具是不能得到那部分特性那部分代码的信息的。下面,让我们来看看一种有用的动态分析工具: gprof.
象大多数Linux程序一样,这个gprof也有它的Unix前身:prof. 在我的HP UX系统上,prof是提供的。要使用它,和使用gprof的方法基本相同,得到的结果也差不多。
那么,让我们直接来看gprof的用法吧。
使用gprof包括三个步骤:
1、编译你的程序,让它能够使用gprof.
2、执行程序,产生一个分析的结果文件。
3、运行gprof,分析结果。
下面,还是结合我们的例子程序,webalizer来讲述gprof的用法吧。
首先,我们要修改webalizer的Makefile. 当你拿到webalizer的源代码包的时候,用tar解开它,就会在其解包后的目录中看到一系列的文件,其中有Makefile.in Makefile.std, 但是没有Makefile文件。你必须用./configure来配置系统,让它来根据你的系统情况自动的生成一个Makefile文件。 Makefile文件生成完之后是这样的(我列出了文件的内容,并且在其中做了解释,汉字的地方是我的解释而不是Makefile文件的内容)
[webalizer-2.01-09]$ cat Makefile
# Generated automatically from Makefile.in by configure.
#
# Makefile for webalizer - a web server log analysis program
#
# Copyright (C) 1997-2000 Bradford L. Barrett (brad@mrunix.net)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version, and provided that the above
# copyright and permission notice is included with all distributed
# copies of this or derived software.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details (file "COPYING").
#
上面的注释说明了这个程序的版权信息,作者,发布的时间,版权等等。
prefix = /export/home/alan/webalizer
上面这一行定义了程序的安装路径
exec_prefix = ${prefix}
这是执行程序的安装路径
BINDIR = ${exec_prefix}/bin
这是二进制文件的安装路径
MANDIR = ${prefix}/man/man1
这是manpages的安装路径
ETCDIR = /export/home/alan/webalizer/etc
这是配置文件的安装路径
CC = gcc
指定编译器是gcc
CFLAGS = -Wall -O2
编译选项
LIBS = -lgd -lpng -lz -lm
连接的库文件
DEFS = -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1
LDFLAGS=
编译选项
INSTALL= /usr/bin/install -c
INSTALL_PROGRAM=${INSTALL}
INSTALL_DATA=${INSTALL} -m 644
安装程序
# where are the GD header files?
GDLIB=/usr/include
# Shouldn't have to touch below here!
all: webalizer
webalizer: webalizer.o webalizer.h hashtab.o hashtab.h
linklist.o linklist.h preserve.o preserve.h
dns_resolv.o dns_resolv.h parser.o parser.h
output.o output.h graphs.o graphs.h lang.h
webalizer_lang.h
$(CC) ${LDFLAGS} -o webalizer webalizer.o hashtab.o linklist.o preserve.o parser.o output.o dns_resolv.o graphs.o ${LIBS}
rm -f webazolver
ln -s webalizer webazolver
定义了编译webalizer目标程序的方法
webalizer.o: webalizer.c webalizer.h parser.h output.h preserve.h
graphs.h dns_resolv.h webalizer_lang.h
$(CC) ${CFLAGS} ${DEFS} -c webalizer.c
parser.o: parser.c parser.h webalizer.h lang.h
$(CC) ${CFLAGS} ${DEFS} -c parser.c
hashtab.o: hashtab.c hashtab.h dns_resolv.h webalizer.h lang.h
$(CC) ${CFLAGS} ${DEFS} -c hashtab.c
linklist.o: linklist.c linklist.h webalizer.h lang.h
$(CC) ${CFLAGS} ${DEFS} -c linklist.c
output.o: output.c output.h webalizer.h preserve.h
hashtab.h graphs.h lang.h
$(CC) ${CFLAGS} ${DEFS} -c output.c
preserve.o: preserve.c preserve.h webalizer.h parser.h
hashtab.h graphs.h lang.h
$(CC) ${CFLAGS} ${DEFS} -c preserve.c
dns_resolv.o: dns_resolv.c dns_resolv.h lang.h webalizer.h
$(CC) ${CFLAGS} ${DEFS} -c dns_resolv.c
graphs.o: graphs.c graphs.h webalizer.h lang.h
$(CC) ${CFLAGS} ${DEFS} -I${GDLIB} -c graphs.c
以上这些是定义了编译.o文件的方法
clean:
rm -f webalizer webazolver *.o usage*.png daily*.png hourly*.png
rm -f ctry*.png *.html *.hist *.current core *.gif
清除编译过的文件
distclean: clean
rm -f webalizer.conf *.tar *.tgz *.Z *.tar.gz
rm -f Makefile webalizer_lang.h config.cache config.log config.status
ln -s lang/webalizer_lang.english webalizer_lang.h
清除编译过的文件, 并且清除了一些配置文件,压缩包文件和通过configure产生的文件,这是用来做重新configure用的。
install: all
$(INSTALL_PROGRAM) webalizer ${BINDIR}/webalizer
$(INSTALL_DATA) webalizer.1 ${MANDIR}/webalizer.1
$(INSTALL_DATA) sample.conf ${ETCDIR}/webalizer.conf.sample
rm -f ${BINDIR}/webazolver
ln -s ${BINDIR}/webalizer ${BINDIR}/webazolver
安装程序
uninstall:
rm -f ${BINDIR}/webalizer
rm -f ${BINDIR}/webazolver
rm -f ${MANDIR}/webalizer.1
rm -f ${ETCDIR}/webalizer.conf.sample
rm -f webalizer_lang.h
ln -s lang/webalizer_lang.english webalizer_lang.h
删除程序
我们需要做的是,改变这两行,以便使用gprof.
CFLAGS = -Wall -O2 -pg
LDFLAGS= -pg
加上了一个编译选项 -pg
接下来,编译程序,
[webalizer-2.01-09]$ make
gcc -Wall -O2 -pg -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1 -c webalizer.c
gcc -Wall -O2 -pg -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1 -c hashtab.c
gcc -Wall -O2 -pg -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1 -c linklist.c
gcc -Wall -O2 -pg -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1 -c preserve.c
gcc -Wall -O2 -pg -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1 -c dns_resolv.c
gcc -Wall -O2 -pg -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1 -c parser.c
gcc -Wall -O2 -pg -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1 -c output.c
gcc -Wall -O2 -pg -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1 -I/usr/include -c graphs.c
gcc -pg -o webalizer webalizer.o hashtab.o linklist.o preserve.o parser.o output.o dns_resolv.o graphs.o -lgd -lpng -lz -lm
rm -f webazolver
ln -s webalizer webazolver
下面使用该程序,记住很重要的一点:这些信息是在你的程序运行的时候动态得到的,所以,如果你的程序的某些特性某些代码没有被执行到,那么凭借动态分析工具是不能得到那部分特性那部分代码的信息的。我有一个不大的日志文件,一共有12459行。其实,如果一个网站的访问量很大的话,这个文件的行数就非常多了,所以,webalizer的效率问题是很重要的。
[webalizer-2.01-09]$ wc -l ../access_log
12459 ../access_log
下面用webalizer来分析这个日志文件并且把结果输出到一个目录中。我用的是很简单的命令行,也没有用任何的配置文件来改变webalizer的缺省动作,也就是说,这次webalizer的运行是按照它的缺省方式来的。
[webalizer-2.01-09]$ ./webalizer ../access_log -o ~/out
Webalizer V2.01-09 (Linux 2.4.2-2) English
Using logfile ../access_log (clf)
Creating output in /export/home/.../out
Hostname for reports is 'example'
Reading history file... webalizer.hist
Generating report for January 2002
Generating summary report
Saving history information...
12459 records in 0.66 seconds
从 webalizer自己的输出中可以看到,处理这个文件一共用了0.66秒。那么,让我们来看看gprof的分析结果。程序运行之后,gprof会在一个目录中产生一个gmon.out文件,缺省情况下是写在程序的运行目录中,但是,我们的例子中,webalizer把结果写到../out目录中,所以,这个文件也在那个目录下。
[webalizer-2.01-09]$ ls ../out/gmon.out
../out/gmon.out
看看它的文件类型:
[webalizer-2.01-09]$ file ../out/gmon.out
../out/gmon.out: GNU prof performance data - version 1
接下来,我们用gprof来分析它的结果,也就是webalizer这一次运行的结果。由于这个分析结果往往是很长的,所以,我把它定向到一个gprof.out的文件中。
[webalizer-2.01-09]$ gprof webalizer ../out/gmon.out > gprof.out
在结果的开头,是每个函数调用所花费的总时间的一个列表,根据花费总时间的长短从高往低排列,然后根据调用的次数从高往低排列,最后根据函数的字母表顺序排列,我只列举前面的几行:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ns/call ns/call name
26.32 0.05 0.05 main
15.79 0.08 0.03 113761 263.71 263.71 isinstr
10.53 0.10 0.02 153021 130.70 218.33 isinlist
10.53 0.12 0.02 24918 802.63 1038.75 ispage
10.53 0.14 0.02 12459 1605.27 1605.27 fmt_logrec
5.26 0.15 0.01 45968 217.54 217.54 hash
5.26 0.16 0.01 37377 267.54 267.54 unescape
5.26 0.17 0.01 12461 802.50 802.50 jdate
5.26 0.18 0.01 12459 802.63 2407.90 parse_record_web
5.26 0.19 0.01 qs_site_cmph
0.00 0.19 0.00 328172 0.00 0.00 isurlchar
0.00 0.19 0.00 66789 0.00 248.42 isinglist
0.00 0.19 0.00 24918 0.00 319.43 put_hnode
0.00 0.19 0.00 12459 0.00 0.00 parse_record
0.00 0.19 0.00 9392 0.00 0.00 put_inode
0.00 0.19 0.00 9392 0.00 241.05 put_unode
0.00 0.19 0.00 8988 0.00 217.54 find_url
0.00 0.19 0.00 8192 0.00 0.00 from_hex
0.00 0.19 0.00 4494 0.00 248.42 srch_string
可以看出,花费时间最长的是main()函数, 我们从程序中也可以看出,作者把很多处理的过程直接放到了主程序中(似乎可以调整一下,以便程序更加结构化模块化?),其次,消耗时间最长的是isinstr, 调用次数也比较多。让我们来实际看一下这个函数:
/*********************************************/
/* ISINSTR - Scan for string in string */
/*********************************************/
int isinstr(char *str, char *cp)
{
char *cp1,*cp2;
cp1=(cp+strlen(cp))-1;
if (*cp=='*')
{
/* if leading wildcard, start from end */
cp2=str+strlen(str)-1;
while ( (cp1!=cp) && (cp2!=str))
{
if (*cp1=='*') return 1;
if (*cp1--!=*cp2--) return 0;
}
if (cp1==cp) return 1;
else return 0;
}
else
{
/* if no leading/trailing wildcard, just strstr */
if (*cp1!='*') return(strstr(str,cp)!=NULL);
/* otherwise do normal forward scan */
cp1=cp; cp2=str;
while (*cp2!='')
{
if (*cp1=='*') return 1;
if (*cp1++!=*cp2++) return 0;
}
if (*cp1=='*') return 1;
else return 0;
}
}
这是一个字符串比较函数,从稍后的说明中可以看出,这个函数主要用于hash表的操作。
第三个函数isinlist也是类似于上一个函数的,主要用于hash表的操作。
/*********************************************/
/* ISINLIST - Test if string is in list */
/*********************************************/
char *isinlist(NLISTPTR list, char *str)
{
NLISTPTR lptr;
lptr=list;
while (lptr!=NULL)
{
if (isinstr(str,lptr->string)) return lptr->string;
lptr=lptr->next;
}
return NULL;
}
需要注意的是另外一个函数,排在第五位的:fmt_logrec
/*********************************************/
/* FMT_LOGREC - terminate log fields w/zeros */
/*********************************************/
void fmt_logrec(char *buffer)
{
char *cp=buffer;
int q=0,b=0,p=0;
while (*cp != '')
{
/* break record up, terminate fields with '' */
switch (*cp)
{
case ' ': if (b || q || p) break; *cp=''; break;
case '"': q^=1; break;
case '[': if (q) break; b++; break;
case ']': if (q) break; if (b>0) b--; break;
case '(': if (q) break; p++; break;
case ')': if (q) break; if (p>0) p--; break;
}
cp++;
}
}
这个函数用于在处理日志文件的时候,预处理这一行数据,把这一行根据用空格分隔的规则分开(同时要注意,在一个括号中的内容虽然有空格,但是不能分为两个栏目),并把每一个栏目变成一个用''结束的字符串,以方便后面的程序进行处理。从这个数据分析结果来看,要提高这个程序的运行效率,缩短运行时间,需要对这几个消耗时间比较长,调用次数比较多的函数进行优化。
接下来是另外一个部分,显示了函数之间的调用关系和调用的函数以及被调用的函数所消耗的时间,调用的次数。
index % time self children called name
[1] 94.7 0.05 0.13 main [1]
0.01 0.02 12459/12459 parse_record_web [4]
0.02 0.01 124591/153021 isinlist [2]
0.02 0.01 24918/24918 ispage [5]
0.00 0.02 62295/66789 isinglist [7]
0.01 0.00 37377/37377 unescape [9]
0.01 0.00 12460/12461 jdate [10]
0.00 0.01 24918/24918 put_hnode [12]
0.00 0.00 9392/9392 put_unode [13]
0.00 0.00 4494/4494 srch_string [15]
0.00 0.00 1/1 month_update_exit [18]
0.00 0.00 1/1 write_month_html [20]
0.00 0.00 328172/328172 isurlchar [21]
0.00 0.00 12459/12459 parse_record [22]
0.00 0.00 9392/9392 put_inode [23]
0.00 0.00 14/14 add_glist [27]
0.00 0.00 3/3 add_nlist [31]
0.00 0.00 3/3 tot_visit [33]
0.00 0.00 1/1 init_counters [47]
0.00 0.00 1/1 get_history [45]
0.00 0.00 1/1 del_hlist [44]
0.00 0.00 1/1 write_main_index [58]
0.00 0.00 1/1 put_history [56]
-----------------------------------------------
0.00 0.00 483/153021 put_unode [13]
0.00 0.00 998/153021 put_hnode [12]
0.00 0.00 26949/153021 ispage [5]
0.02 0.01 124591/153021 main [1]
[2] 17.6 0.02 0.01 153021 isinlist [2]
0.01 0.00 50845/113761 isinstr [3]
-----------------------------------------------
0.01 0.00 50845/113761 isinlist [2]
0.02 0.00 62916/113761 isinglist [7]
[3] 15.8 0.03 0.00 113761 isinstr [3]
-----------------------------------------------
0.01 0.02 12459/12459 main [1]
[4] 15.8 0.01 0.02 12459 parse_record_web [4]
0.02 0.00 12459/12459 fmt_logrec [6]
-----------------------------------------------
0.02 0.01 24918/24918 main [1]
[5] 13.6 0.02 0.01 24918 ispage [5]
0.00 0.00 26949/153021 isinlist [2]
-----------------------------------------------
0.02 0.00 12459/12459 parse_record_web [4]
[6] 10.5 0.02 0.00 12459 fmt_logrec [6]
-----------------------------------------------
0.00 0.00 4494/66789 srch_string [15]
0.00 0.02 62295/66789 main [1]
[7] 8.7 0.00 0.02 66789 isinglist [7]
0.02 0.00 62916/113761 isinstr [3]
-----------------------------------------------
0.00 0.00 467/45968 update_entry [17]
0.00 0.00 614/45968 update_exit [16]
0.00 0.00 8988/45968 find_url [14]
0.00 0.00 9922/45968 put_unode [13]
0.01 0.00 25977/45968 put_hnode [12]
[8] 5.3 0.01 0.00 45968 hash [8]
-----------------------------------------------
0.01 0.00 37377/37377 main [1]
[9] 5.3 0.01 0.00 37377 unescape [9]
0.00 0.00 8192/8192 from_hex [24]
-----------------------------------------------
(下面还有,为了节省篇幅,略去)
这一段分析结果报告显示了函数之间的调用关系,以及消耗的时间,被调用函数消耗的时间,和调用次数。每一个函数都有一个index数值来表示(这在后面有描述), time显示了该函数及其调用的函数消耗的时间self显示了该函数自身所消耗的时间,children显示了该函数调用的子函数消耗的时间,called显示了该函数调用相应子函数的次数,name显示了该函数的名字。在报告的最后,有一张表,列出了所有的函数及其index的对应关系。这张表可以让你快速的在报告中找到相应的函数。
Index by function name
[27] add_glist [10] jdate [56] put_history
[31] add_nlist [48] load_agent_array [12] put_hnode
[34] calc_arc [49] load_ident_array [23] put_inode
[29] cur_time [50] load_ref_array [13] put_unode
[42] daily_total_table [35] load_site_array [11] qs_site_cmph
[43] day_graph3 [51] load_srch_array [15] srch_string
[44] del_hlist [36] load_url_array [57] top_ctry_table
[14] find_url [1] main [37] top_entry_table
[6] fmt_logrec [19] month_graph6 [38] top_sites_table
[24] from_hex [52] month_links [39] top_urls_table
[45] get_history [53] month_total_table [33] tot_visit
[8] hash [18] month_update_exit [9] unescape
[46] hourly_total_table [28] new_glist [17] update_entry
[47] init_counters [25] new_hnode [16] update_exit
[30] init_graph [32] new_nlist [40] write_html_head
[7] isinglist [26] new_unode [41] write_html_tail
[2] isinlist [54] open_out_file [58] write_main_index
[3] isinstr [22] parse_record [20] write_month_html
[5] ispage [4] parse_record_web [59] year_graph6x
[21] isurlchar [55] pie_chart
根据这份报告,结合前面的ctags, 我们就能比较清晰的来阅读这个程序了。关于gprof的其他细节问题,可以参考man page, 并自己练习体会。
好,上面讲了如何用静态和动态的两种方法来分析源代码,下面,就webalizer这个程序中的一段程序output.c来做一下实践。(这一段程序我在上一篇文章中并没有涉及到,因为从篇幅上来讲,这一个文件是最大的,而且函数之间的调用比较复杂,现在介绍了这几个工具,就可以比较轻松的来阅读了。实际上,这个文件中的函数,直到整个主程序的结尾才被调用了一次。在main函数中,结尾部分有这么一段程序:
if (total_rec > (total_ignore+total_bad)) /* did we process any? */
{
if (incremental)
{
if (save_state()) /* incremental stuff */
{
/* Error: Unable to save current run data */
if (verbose) fprintf(stderr,"%s ",msg_data_err);
unlink(state_fname);
}
}
month_update_exit(rec_tstamp); /* calculate exit pages */
write_month_html(); /* write monthly HTML file */
write_main_index(); /* write main HTML file */
put_history(); /* write history */
}
但是,如果有细心的读者会发现,这个函数在main()程序中出现了2次,另外一次在main()的中间部分出现过:
/* check for month change */
if (cur_month != rec_month)
{
/* if yes, do monthly stuff */
t_visit=tot_visit(sm_htab);
month_update_exit(req_tstamp); /* process exit pages */
write_month_html(); /* generate HTML for month */
clear_month();
cur_month = rec_month; /* update our flags */
cur_year = rec_year;
f_day=l_day=rec_day;
}
但是,很明显这一段没有被执行。为什么呢?看看刚刚提过的函数调用的表:
0.00 0.00 1/1 write_month_html [20]
实际上,这个函数就是output.c的主要函数了。我们从它开始入手分析。
int write_month_html()
{
int i;
char html_fname[256]; /* filename storage areas... */
char png1_fname[32];
char png2_fname[32];
char buffer[BUFSIZE]; /* scratch buffer */
char dtitle[256];
char htitle[256];
if (verbose>1)
printf("%s %s %d ",msg_gen_rpt, l_month[cur_month-1], cur_year);
/* update history */
i=cur_month-1;
hist_month[i] = cur_month;
hist_year[i] = cur_year;
hist_hit[i] = t_hit;
hist_files[i] = t_file;
hist_page[i] = t_page;
hist_visit[i] = t_visit;
hist_site[i] = t_site;
hist_xfer[i] = t_xfer/1024;
hist_fday[i] = f_day;
hist_lday[i] = l_day;
这一段是准备数据,hist_XXX这一组数组是干什么的呢?如果没有阅读过其它代码,很可能就迷惑不解。让我们来看一看。用ex命令:ta hist_month, 跳转到hist_month申明出现的地方,
/* local variables */
int hist_month[12], hist_year[12]; /* arrays for monthly total */
u_long hist_hit[12]; /* calculations: used to */
u_long hist_files[12]; /* produce index.html */
u_long hist_site[12]; /* these are read and saved */
double hist_xfer[12]; /* in the history file */
u_long hist_page[12];
u_long hist_visit[12];
这一组数组是为了存储hist数值的。那么,用vi的搜索功能看一看它们的数值的变化情况:
在函数get_history()中,
void get_history()
{
int i,numfields;
FILE *hist_fp;
char buffer[BUFSIZE];
/* first initalize internal array */
for (i=0;i<12;i++)
{
hist_month[i]=hist_year[i]=hist_fday[i]=hist_lday[i]=0;
hist_hit[i]=hist_files[i]=hist_site[i]=hist_page[i]=hist_visit[i]=0;
hist_xfer[i]=0.0;
}
先把数组的数值全都赋为0。
hist_fp=fopen(hist_fname,"r");
if (hist_fp)
{
if (verbose>1) printf("%s %s ",msg_get_hist,hist_fname);
while ((fgets(buffer,BUFSIZE,hist_fp)) != NULL)
{
i = atoi(buffer) -1;
if (i>11)
{
if (verbose)
fprintf(stderr,"%s (mth=%d) ",msg_bad_hist,i+1);
continue;
}
/* month# year# requests files sites xfer firstday lastday */
numfields = sscanf(buffer,"%d %d %lu %lu %lu %lf %d %d %lu %lu",
&hist_month[i],
&hist_year[i],
&hist_hit[i],
&hist_files[i],
&hist_site[i],
&hist_xfer[i],
&hist_fday[i],
&hist_lday[i],
&hist_page[i],
&hist_visit[i]);
if (numfields==8) /* kludge for reading 1.20.xx history files */
{
hist_page[i] = 0;
hist_visit[i] = 0;
}
}
fclose(hist_fp);
}
从history文件中读出数据,赋值给hist数组。
另外一个相关的出现的地方是put_history()函数,把hist数组中的数值写到history文件中去。
void put_history()
{
int i;
FILE *hist_fp;
hist_fp = fopen(hist_fname,"w");
if (hist_fp)
{
if (verbose>1) printf("%s ",msg_put_hist);
for (i=0;i<12;i++)
{
if ((hist_month[i] != 0) && (hist_hit[i] != 0))
{
fprintf(hist_fp,"%d %d %lu %lu %lu %.0f %d %d %lu %lu ",
hist_month[i],
hist_year[i],
hist_hit[i],
hist_files[i],
hist_site[i],
hist_xfer[i],
hist_fday[i],
hist_lday[i],
hist_page[i],
hist_lday[i],
hist_page[i],
hist_visit[i]);
}
}
fclose(hist_fp);
回忆一下webalizer文件包中提供的readme文件,history文件存放了上一次运行分析日志时候的结果,以便下一次再次拿出来使用。好,明白了hist_数组的意义,我们再回到write_month_html()函数。用Control+T可以回到上次tag的地方,继续阅读该函数的代码。
/* fill in filenames */
sprintf(html_fname,"usage_%04d%02d.%s",cur_year,cur_month,html_ext);
sprintf(png1_fname,"daily_usage_%04d%02d.png",cur_year,cur_month);
sprintf(png2_fname,"hourly_usage_%04d%02d.png",cur_year,cur_month);
这是准备.html的文件名。其中,png是图形文件的名字。
/* Do URL related stuff here, sorting appropriately */
if ( (a_ctr=load_url_array(NULL)) )
{
if ( (u_array=malloc(sizeof(UNODEPTR)*(a_ctr))) !=NULL )
{
a_ctr=load_url_array(u_array); /* load up our sort array */
if (ntop_urls || dump_urls)
{
qsort(u_array,a_ctr,sizeof(UNODEPTR),qs_url_cmph);
if (ntop_urls) top_urls_table(0); /* Top URL's (by hits) */
if (dump_urls) dump_all_urls(); /* Dump URLS tab file */
}
if (ntop_urlsK) /* Top URL's (by kbytes) */
{qsort(u_array,a_ctr,sizeof(UNODEPTR),qs_url_cmpk); top_urls_table(1); }
if (ntop_entry) /* Top Entry Pages */
{qsort(u_array,a_ctr,sizeof(UNODEPTR),qs_url_cmpn); top_entry_table(0);}
if (ntop_exit) /* Top Exit Pages */
{qsort(u_array,a_ctr,sizeof(UNODEPTR),qs_url_cmpx); top_entry_table(1);}
free(u_array);
}
else if (verbose) fprintf(stderr,"%s [u_array] ",msg_nomem_tu); /* err */
}
这一段是准备在main()函数中从文件得到的数据,把这些数组拷贝过来。主要要读懂load_url_array()的意义。下面,用:ta load_url_array,跳到该函数。
u_long load_url_array(UNODEPTR *pointer)
{
UNODEPTR uptr;
int i;
u_long ctr = 0;
/* load the array */
for (i=0;i
uptr=um_htab[i];
while (uptr!=NULL)
{
if (pointer==NULL) ctr++; /* fancy way to just count 'em */
else *(pointer+ctr++)=uptr; /* otherwise, really do the load */
uptr=uptr->next;
}
}
return ctr; /* return number loaded */
}
这段程序很小,但是它执行了两种不同的动作,根据pointer参数的不同。如果pointer是NULL, 那么,它统计了在um_htab这个hash表中元素的个数,并把个数返回。如果pointer不为NULL, 就将um_htab所指向的hash表中的元素拷贝到pointer所指向的位置,并且返回拷贝过去的元素的总数。Control+T, 返回到刚才的地方,继续阅读。
if ( (u_array=malloc(sizeof(UNODEPTR)*(a_ctr))) !=NULL )
{
a_ctr=load_url_array(u_array); /* load up our sort array */
在统计了个数之后,为u_array分配空间,正好能够装下um_htab中的所有元素,然后就把um_htab中的所有元素拷贝到u_array所指向的空间中来,并且接下来,
/* Do URL related stuff here, sorting appropriately */
if ( (a_ctr=load_url_array(NULL)) )
{
if ( (u_array=malloc(sizeof(UNODEPTR)*(a_ctr))) !=NULL )
{
a_ctr=load_url_array(u_array); /* load up our sort array */
if (ntop_urls' 'dump_urls)
{
qsort(u_array,a_ctr,sizeof(UNODEPTR),qs_url_cmph);
if (ntop_urls) top_urls_table(0); /* Top URL's (by hits) */
if (dump_urls) dump_all_urls(); /* Dump URLS tab file */
}
if (ntop_urlsK) /* Top URL's (by kbytes) */
{qsort(u_array,a_ctr,sizeof(UNODEPTR),qs_url_cmpk); top_urls_table(1); }
if (ntop_entry) /* Top Entry Pages */
{qsort(u_array,a_ctr,sizeof(UNODEPTR),qs_url_cmpn); top_entry_table(0);}
if (ntop_exit) /* Top Exit Pages */
{qsort(u_array,a_ctr,sizeof(UNODEPTR),qs_url_cmpx); top_entry_table(1);}
free(u_array);
}
else if (verbose) fprintf(stderr,"%s [u_array] ",msg_nomem_tu); /* err */
}
/* do hostname (sites) related stuff here, sorting appropriately... */
if ( (a_ctr=load_site_array(NULL)) )
{
if ( (h_array=malloc(sizeof(HNODEPTR)*(a_ctr))) !=NULL )
{
a_ctr=load_site_array(h_array); /* load up our sort array */
if (ntop_sites' 'dump_sites)
{
qsort(h_array,a_ctr,sizeof(HNODEPTR),qs_site_cmph);
if (ntop_sites) top_sites_table(0); /* Top sites table (by hits) */
if (dump_sites) dump_all_sites(); /* Dump sites tab file */
}
if (ntop_sitesK) /* Top Sites table (by kbytes) */
{
qsort(h_array,a_ctr,sizeof(HNODEPTR),qs_site_cmpk);
top_sites_table(1);
}
free(h_array);
}
else if (verbose) fprintf(stderr,"%s [h_array] ",msg_nomem_ts); /* err */
}
用 qsort函数排序。这个函数根据最后一个参数(这是一个函数指针)来给u_array排序。函数指针指向的函数表示了排序的标准,例如,用返回一个小于,等于,大于0的整数,来表示两个元素的小于,等于,大于关系。排序之后,或者是输出一部分元素(比如top_sites_table())或者是输出全部元素(dump_all_urls())。在这里,我们比较感兴趣的是qsort这个函数的最后一个参数,注意到,有好多个这种比较大小的函数,我们从qs_url_cmph()开始看。用:ta qs_url_cmph转到该函数。
/*********************************************/
/* QS_URL_CMPH - QSort compare URL by hits */
/*********************************************/
int qs_url_cmph(const void *cp1, const void *cp2)
{
u_long t1, t2;
t1=(*(UNODEPTR *)cp1)->count;
t2=(*(UNODEPTR *)cp2)->count;
if (t1!=t2) return (t2
return strcmp( (*(UNODEPTR *)cp1)->string,
(*(UNODEPTR *)cp2)->string );
}
这个函数是根据unode中的count的大小来判断两个元素的大小。为了方便,我们把unode的结构定义也列在下面。
struct unode { char *string; /* url hash table structure */
int flag; /* Object type (REG, HIDE, GRP) */
u_long count; /* requests counter */
u_long files; /* files counter */
u_long entry; /* entry page counter */
u_long exit; /* exit page counter */
double xfer; /* xfer size in bytes */
struct unode *next; }; /* pointer to next node */
/*********************************************/
/* QS_SITE_CMPK - QSort cmp site by bytes */
/*********************************************/
int qs_site_cmpk(const void *cp1, const void *cp2)
{
double t1, t2;
t1=(*(HNODEPTR *)cp1)->xfer;
t2=(*(HNODEPTR *)cp2)->xfer;
if (t1!=t2) return (t2
return strcmp( (*(HNODEPTR *)cp1)->string,
(*(HNODEPTR *)cp2)->string );
}
这个函数是根据hnode中的xfer的大小来判断两个元素的大小。
/*********************************************/
/* QS_URL_CMPH - QSort compare URL by hits */
/*********************************************/
int qs_url_cmph(const void *cp1, const void *cp2)
{
u_long t1, t2;
t1=(*(UNODEPTR *)cp1)->count;
t2=(*(UNODEPTR *)cp2)->count;
if (t1!=t2) return (t2
return strcmp( (*(UNODEPTR *)cp1)->string,
(*(UNODEPTR *)cp2)->string );
}
这个函数是根据unode中的count的大小来判断两个元素的大小。
/*********************************************/
/* QS_URL_CMPK - QSort compare URL by bytes */
/*********************************************/
int qs_url_cmpk(const void *cp1, const void *cp2)
{
double t1, t2;
t1=(*(UNODEPTR *)cp1)->xfer;
t2=(*(UNODEPTR *)cp2)->xfer;
if (t1!=t2) return (t2
return strcmp( (*(UNODEPTR *)cp1)->string,
(*(UNODEPTR *)cp2)->string );
}
这个函数是根据unode中的xfer的大小来判断两个元素的大小。
/*********************************************/
/* QS_URL_CMPN - QSort compare URL by entry */
/*********************************************/
int qs_url_cmpn(const void *cp1, const void *cp2)
{
double t1, t2;
t1=(*(UNODEPTR *)cp1)->entry;
t2=(*(UNODEPTR *)cp2)->entry;
if (t1!=t2) return (t2
return strcmp( (*(UNODEPTR *)cp1)->string,
(*(UNODEPTR *)cp2)->string );
}
这个函数是根据unode中的entry的大小来判断两个元素的大小。
/*********************************************/
/* QS_URL_CMPX - QSort compare URL by exit */
/*********************************************/
int qs_url_cmpx(const void *cp1, const void *cp2)
{
double t1, t2;
t1=(*(UNODEPTR *)cp1)->exit;
t2=(*(UNODEPTR *)cp2)->exit;
if (t1!=t2) return (t2
return strcmp( (*(UNODEPTR *)cp1)->string,
(*(UNODEPTR *)cp2)->string );
}
这个函数是根据unode中的exit的大小来判断两个元素的大小。
/*********************************************/
/* QS_REF_CMPH - QSort compare Refs by hits */
/*********************************************/
int qs_ref_cmph(const void *cp1, const void *cp2)
{
u_long t1, t2;
t1=(*(RNODEPTR *)cp1)->count;
t2=(*(RNODEPTR *)cp2)->count;
if (t1!=t2) return (t2
return strcmp( (*(RNODEPTR *)cp1)->string,
(*(RNODEPTR *)cp2)->string );
}
这个函数是根据rnode中的count的大小来判断两个元素的大小。
/*********************************************/
/* QS_AGNT_CMPH - QSort cmp Agents by hits */
/*********************************************/
int qs_agnt_cmph(const void *cp1, const void *cp2)
{
u_long t1, t2;
t1=(*(ANODEPTR *)cp1)->count;
t2=(*(ANODEPTR *)cp2)->count;
if (t1!=t2) return (t2
return strcmp( (*(ANODEPTR *)cp1)->string,
(*(ANODEPTR *)cp2)->string );
}
这个函数是根据anode中的count的大小来判断两个元素的大小。
/*********************************************/
/* QS_SRCH_CMPH - QSort cmp srch str by hits */
/*********************************************/
int qs_srch_cmph(const void *cp1, const void *cp2)
{
u_long t1, t2;
t1=(*(SNODEPTR *)cp1)->count;
t2=(*(SNODEPTR *)cp2)->count;
if (t1!=t2) return (t2
return strcmp( (*(SNODEPTR *)cp1)->string,
(*(SNODEPTR *)cp2)->string );
}
这个函数是根据snode中的count的大小来判断两个元素的大小。
/*********************************************/
/* QS_IDENT_CMPH - QSort cmp ident by hits */
/*********************************************/
int qs_ident_cmph(const void *cp1, const void *cp2)
{
u_long t1, t2;
t1=(*(INODEPTR *)cp1)->count;
t2=(*(INODEPTR *)cp2)->count;
if (t1!=t2) return (t2
return strcmp( (*(INODEPTR *)cp1)->string,
(*(INODEPTR *)cp2)->string );
}
这个函数是根据inode中的count的大小来判断两个元素的大小。分析清楚了这些函数,下面的工作就容易得多了。因为对于每一个hash表,如 unode, snode等等,都有一套这样的函数,load_XXX_array和相应的qsort及其比较大小的函数,下面的程序代码段只不过是针对每一种数据给出一套新的处理办法,本质上是一样的。比如,
/* do hostname (sites) related stuff here, sorting appropriately... */
if ( (a_ctr=load_site_array(NULL)) )
{
if ( (h_array=malloc(sizeof(HNODEPTR)*(a_ctr))) !=NULL )
{
a_ctr=load_site_array(h_array); /* load up our sort array */
if (ntop_sites' 'dump_sites)
{
qsort(h_array,a_ctr,sizeof(HNODEPTR),qs_site_cmph);
if (ntop_sites) top_sites_table(0); /* Top sites table (by hits) */
if (dump_sites) dump_all_sites(); /* Dump sites tab file */
}
if (ntop_sitesK) /* Top Sites table (by kbytes) */
{
qsort(h_array,a_ctr,sizeof(HNODEPTR),qs_site_cmpk);
top_sites_table(1);
}
free(h_array);
}
else if (verbose) fprintf(stderr,"%s [h_array] ",msg_nomem_ts); /* err */
}
针对hnode排序并且输出。
/* do referrer related stuff here, sorting appropriately... */
if ( (a_ctr=load_ref_array(NULL)) )
{
if ( (r_array=malloc(sizeof(RNODEPTR)*(a_ctr))) != NULL)
{
a_ctr=load_ref_array(r_array);
if (ntop_refs' 'dump_refs)
{
qsort(r_array,a_ctr,sizeof(RNODEPTR),qs_ref_cmph);
if (ntop_refs) top_refs_table(); /* Top referrers table */
if (dump_refs) dump_all_refs(); /* Dump referrers tab file */
}
free(r_array);
}
else if (verbose) fprintf(stderr,"%s [r_array] ",msg_nomem_tr); /* err */
}
针对rnode排序并且输出。
其实,到了程序的结尾,我们能够很清楚的认识到这个程序的流程结构了,那就是,从日志文件中得到每一条日志记录,将其分别放入到各个hash表中,如 hnode, unode等等,最后根据不同的标准来排序输出。当然,这中间我省略了一些错误处理,格式化的工作。到这里为止,我们已经完全结束了这个程序的阅读,以及如何采用工具去分析,辅助阅读源代码。当然,仅仅这些是不够的。找来一个有源代码程序,并且实际的去阅读来作为练习是最好的学习和实践的方法。