EOS究竟是什麼詞的縮寫,我猜應該是Error of System。最先接觸它,是在UT那會。不過那會它是被設計成一個很大的數組,也沒有被包含調用函數和行號,又或是時間,只是些計數。編碼時,加減一個EOS仍是有點小麻煩,除了調用點外,大概須要修改多個點,好比先要定義,而後打印函數裏的名字翻譯等。開始的時候還行,但錯誤碼多了後,更新就有點麻煩,只好又設計了個腳原本自動生成定義和打印函數。但終究仍是不算方便,開發人員有時候更願意用Trace來打印。固然,EOS不是萬能的,有時候用Trace真比EOS更好,固然,權衡使用纔是最好的。linux
何時須要用EOS?當程序須要長期運行,且但願儘量的不影響程序大事務量處理的時候。好比一個電話交換系統,或是一個網絡服務後臺。如果用Trace,除非選擇性的對某一特定用戶會話使能,不然系統必然被巨量的打印拖垮。而若是是針對某一特定用戶跟蹤,則等於選擇性的忽視系統運行中出現的錯誤,不利於發現壓力測試中出現的故障,尤爲是一些隱藏的故障,和一些難於重現的故障。shell
離開UT後,我改良了EOS的設計,這裏面程序自己的知識很是少,靠的是靈活運用編譯知識。數組
先說說數據結構和代碼:安全
typedef struct ERROR_NO_TYPE { const char* errstr; const char* function; int line; int count; int when; } PACKED Eos_t;
EOS的數據結構很簡單,兩個常量字符串指針,而後是文件行號,計數和時間。在32bits的系統上,一共佔20個字節。網絡
哥提供的代碼裏,預分配的NHASH爲19997個記錄,共計390Kbytes空間。對於通常系統來講,這個的內存開銷不存在什麼問題。固然,它根據須要能夠隨意調整,以下:數據結構
/* simple eosNHash of fixed size */ #define EOS_NHASH 19997 Eos_t eosNHash[EOS_NHASH] = { {0, }, };
NHASH能夠被總體清空的數據結構,但不支持刪除某個節點操做。ide
接下來,就是實現代碼,更簡單:函數
/* errstr table */ /* hash a errstr */ static unsigned ErrnoValue(const char *errstr) { return *(unsigned*)errstr; } int zEosPeg(const char* errstr, const char* function, int line) { if(!g_zEosEnabled || !errstr) return -1; int num = ErrnoValue(errstr) % EOS_NHASH; int count = EOS_NHASH; while(--count >= 0) { Eos_t *ptr = &eosNHash[num]; if(!ptr->errstr) //not exist yet { ptr->errstr = errstr; ptr->function = function; ptr->line = line; ptr->count = 1; ptr->when = zTime(); return 1; } else if(ptr->errstr == errstr && ptr->function == function && ptr->line == line) { ptr->count += 1; ptr->when = zTime(); } if(++num >= EOS_NHASH) num=0; /* try the next entry */ } //overflow return -1; } int zEosShow(const char* errstr) { int num; zTraceP("EOS Enabled: %s\n", g_zEosEnabled?"YES":"NO"); for(num=0; num<EOS_NHASH; num++) { Eos_t *ptr = &eosNHash[num]; if(!ptr->errstr) continue; if(errstr) { if(!strcasestr(ptr->errstr, errstr)) continue; zTraceP("[%5d]: %s %d -- %s:%d %s\n", num, ptr->errstr, ptr->count, ptr->function, ptr->line, zCTime(&ptr->when)); } else { zTraceP("[%5d]: %s %d -- %s:%d %s\n", num, ptr->errstr, ptr->count, ptr->function, ptr->line, zCTime(&ptr->when)); } } return 0; }
嗯,確實就這麼幾行代碼,一個是往hash中添加新的EOS記錄或是統計,另外一個是輸出打印錯誤碼的信息。最後,是個用戶頭文件,以下:工具
IMPORT int zEosPeg(const char* errstr, const char* function, int line); IMPORT int zEosShow(const char* errstr); /*overrides the per nodal SET_EOS*/ #undef SET_EOS #define SET_EOS(eos) zEosPeg(_STR(eos), __FUNCTION__, __LINE__)
固然,用戶在使用的時候,不建議直接調用這裏的peg函數,那樣的話,就拒絕了哥的好意。程序應該使用那個宏定義,而別使用我在footprint.c裏面的那段自測試代碼樣式。那個,是反面教材,用來描述EOS怎麼工做的!性能
咱們能夠像下面這樣使用EOS:
/*---------------------------------------------------------- File Name : xxx.c Description: Author : hhao020@gmail.com (bug fixing and consulting) Date : 2007-05-15 ------------------------------------------------------------*/ #include "zType_Def.h" #include "zFootprintApi.h" int TestEosPeg() { SET_EOS(put any thing you like here. only no comma); SET_EOS(ooh... really?); SET_EOS(ooh... really?); SET_EOS(ooh... really?); SET_EOS(sure. just try!); return 0; }
而後,在CSHELL下運行zEosShow(),就會有這樣的結果:
cshell_prj $ bin/target_a.linux.i32.exe ->TestEosPeg() $1/> TestEosPeg() = 0 (0x0) <FUNCALL : size=0> ->zEosShow() $2/> zEosShow() EOS Enabled: YES [ 4839]: put any thing you like here. only no comma 1 -- TestEosPeg:13 Mon Dec 7 14:53:46 2015 [13012]: ooh... really? 1 -- TestEosPeg:14 Mon Dec 7 14:53:46 2015 [13013]: ooh... really? 1 -- TestEosPeg:15 Mon Dec 7 14:53:46 2015 [13014]: ooh... really? 1 -- TestEosPeg:16 Mon Dec 7 14:53:46 2015 [15323]: sure. just try! 1 -- TestEosPeg:17 Mon Dec 7 14:53:46 2015 = 0 (0x0) <FUNCALL : size=153> ->TestEosPeg() $3/> TestEosPeg() = 0 (0x0) <FUNCALL : size=344> ->zEosShow() $4/> zEosShow() EOS Enabled: YES [ 4839]: put any thing you like here. only no comma 2 -- TestEosPeg:13 Mon Dec 7 14:55:07 2015 [ 4840]: put any thing you like here. only no comma 1 -- TestEosPeg:13 Mon Dec 7 14:55:07 2015 [13012]: ooh... really? 2 -- TestEosPeg:14 Mon Dec 7 14:55:07 2015 [13013]: ooh... really? 2 -- TestEosPeg:15 Mon Dec 7 14:55:07 2015 [13014]: ooh... really? 2 -- TestEosPeg:16 Mon Dec 7 14:55:07 2015 [13015]: ooh... really? 1 -- TestEosPeg:14 Mon Dec 7 14:55:07 2015 [13016]: ooh... really? 1 -- TestEosPeg:15 Mon Dec 7 14:55:07 2015 [13017]: ooh... really? 1 -- TestEosPeg:16 Mon Dec 7 14:55:07 2015 [15323]: sure. just try! 2 -- TestEosPeg:17 Mon Dec 7 14:55:07 2015 [15324]: sure. just try! 1 -- TestEosPeg:17 Mon Dec 7 14:55:07 2015 = 0 (0x0) <FUNCALL : size=153> ->
如今,講一講原理,和一些使用注意事項。
首先是關於那個字符串指針的問題。有人會猶豫,怎麼就只存個指針,而不是個字符串呢?這個,須要理解下編譯器和ELF文件格式。程序源碼裏出現的字符串,最終都會出如今ELF文件當中,程序加載後,也會出如今內存中。而使用這類字符串天然是安全的。
或許有人猶豫,NHASH是最好選擇麼?看你想這麼用。
NHASH的最大好處是,程序加載後,獲得的就已是初始化過的列表。如此一來,你能夠在更早的初始化代碼里加EOS,而不用擔憂這個service是否是已經可以提供。NHASH是輕量級的,可以方便你移植EOS到任何須要的程序中。
若是你的系統能夠在加載後,在有必要調用EOS前,可以執行一個初始化函數,那固然能夠選擇avl等一類更高效的數據結構。NHASH確實存在退化問題,當EOS不少,在NHASH條目裏達到必定比例後,確實存在嚴重的性能問題。不過這並非那麼容易發生的,若是咱們記住讓NHASH條目遠遠大於實際可能的條目數,且僅在適當須要時提供EOS。無節制的使用EOS,不僅是性能問題,更多的是,你獲得太多的EOS統計項,就好似你擁有太多書而看不過來同樣糟糕。
函數名和行號是否必要?我建議提供,要否則,SET_EOS會把不一樣的統計點當成相同錯誤碼進行統計。
此外,能夠考慮設置一個開關去使能它,默認關閉,這個世界老是有人喜歡嘰歪,跟你談什麼性能問題,既然有人反對,那就關掉它,省得費口舌。跟不一樣性能的人談性能問題,會玷污智商,因此千萬別爭,這時告訴他們,EOS只是個測試工具而已。
須要作個重置統計的接口,這樣能夠方便測試期發現問題。還能夠作一個輸出函數,將EOS按時間進行排序輸出,這樣,許多時候可以看出程序的運行軌跡,對於差錯頗有幫助。FSM Trace裏其實也集成了EOS,不過須要在FSM的調用裏peg,由用戶來完成。有興趣研究我給的FSM的童鞋能夠試試。
最後,EOS不是萬能的,實踐上須要配合Trace功能,即日誌打印功能。zLib裏的zTrace是個不錯的選擇,有須要的不妨一讀!