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是个不错的选择,有须要的不妨一读!