[轉載linuxaid文章]如何閱讀源代碼

http://www.linuxaid.com.cn/articles/8/6/861776524.shtml

感謝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, 也可以到http://ctags.sourceforge.net/下載它。它的作者是Darren Hiebert <[email protected]>, <[email protected]>, 主頁地址是http://darren.hiebert.com/

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 /[email protected]/
!_TAG_PROGRAM_NAME Exuberant Ctags //
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /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 (http://www.gedanken.demon.co.uk/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(), <test.c 2>
2 f: int(), <test.c 8>
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 ([email protected])
#
# 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
                                                 <spontaneous>
[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<MAXHASH;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<t1)?-1:1;
   /* if hits are the same, we sort by url instead */
   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<t1)?-1:1;
   /* if xfer bytes are the same, we sort by hostname instead */
   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<t1)?-1:1;
   /* if hits are the same, we sort by url instead */
   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<t1)?-1:1;
   /* if xfer bytes are the same, we sort by url instead */
   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<t1)?-1:1;
   /* if xfer bytes are the same, we sort by url instead */
   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<t1)?-1:1;
   /* if xfer bytes are the same, we sort by url instead */
   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<t1)?-1:1;
   /* if hits are the same, we sort by referrer URL instead */
   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<t1)?-1:1;
   /* if hits are the same, we sort by agent string instead */
   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<t1)?-1:1;
   /* if hits are the same, we sort by search string instead */
   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<t1)?-1:1;
   /* if hits are the same, sort by ident (username) string instead */
   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等等,最後根據不同的標準來排序輸出。當然,這中間我省略了一些錯誤處理,格式化的工作。到這裏爲止,我們已經完全結束了這個程序的閱讀,以及如何採用工具去分析,輔助閱讀源代碼。當然,僅僅這些是不夠的。找來一個有源代碼程序,並且實際的去閱讀來作爲練習是最好的學習和實踐的方法。

責任編輯:ariesram(02-01-21 09:10)
發佈了47 篇原創文章 · 獲贊 1 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章