EOS到底是什么词的缩写,我猜应该是Error of System。最早接触它,是在UT那会。不过那会它是被设计成一个很大的数组,也没有被包含调用函数和行号,又或是时间,只是些计数。编码时,加减一个EOS还是有点小麻烦,除了调用点外,大概需要修改多个点,比如先要定义,然后打印函数里的名字翻译等。开始的时候还行,但错误码多了后,更新就有点麻烦,只好又设计了个脚本来自动生成定义和打印函数。但终究还是不算方便,开发人员有时候更愿意用Trace来打印。当然,EOS不是万能的,有时候用Trace真比EOS更好,当然,权衡使用才是最好的。

什么时候需要用EOS?当程序需要长期运行,且希望尽可能的不影响程序大事务量处理的时候。比如一个电话交换系统,或是一个网络服务后台。若是用Trace,除非选择性的对某一特定用户会话使能,否则系统必然被巨量的打印拖垮。而如果是针对某一特定用户跟踪,则等于选择性的忽视系统运行中出现的错误,不利于发现压力测试中出现的故障,尤其是一些隐藏的故障,和一些难于重现的故障。

离开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] = { {, }, };

NHASH可以被整体清空的数据结构,但不支持删除某个节点操作。

接下来,就是实现代码,更简单:

/* 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 -; int num = ErrnoValue(errstr) % EOS_NHASH;
int count = EOS_NHASH; while(--count >= )
{
Eos_t *ptr = &eosNHash[num]; if(!ptr->errstr) //not exist yet
{
ptr->errstr = errstr;
ptr->function = function;
ptr->line = line;
ptr->count = ;
ptr->when = zTime(); return ;
}
else if(ptr->errstr == errstr && ptr->function == function && ptr->line == line)
{
ptr->count += ;
ptr->when = zTime();
} if(++num >= EOS_NHASH) num=; /* try the next entry */
} //overflow
return -;
} int zEosShow(const char* errstr)
{
int num; zTraceP("EOS Enabled: %s\n", g_zEosEnabled?"YES":"NO"); for(num=; 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 ;
}

嗯,确实就这么几行代码,一个是往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 ;
}

然后,在CSHELL下运行zEosShow(),就会有这样的结果:

cshell_prj $ bin/target_a.linux.i32.exe
->TestEosPeg() $/> TestEosPeg() = (0x0) <FUNCALL : size=>
->zEosShow() $/> zEosShow() EOS Enabled: YES
[ ]: put any thing you like here. only no comma -- TestEosPeg: Mon Dec :: []: ooh... really? -- TestEosPeg: Mon Dec :: []: ooh... really? -- TestEosPeg: Mon Dec :: []: ooh... really? -- TestEosPeg: Mon Dec :: []: sure. just try! -- TestEosPeg: Mon Dec :: = (0x0) <FUNCALL : size=>
->TestEosPeg() $/> TestEosPeg() = (0x0) <FUNCALL : size=>
->zEosShow() $/> zEosShow() EOS Enabled: YES
[ ]: put any thing you like here. only no comma -- TestEosPeg: Mon Dec :: [ ]: put any thing you like here. only no comma -- TestEosPeg: Mon Dec :: []: ooh... really? -- TestEosPeg: Mon Dec :: []: ooh... really? -- TestEosPeg: Mon Dec :: []: ooh... really? -- TestEosPeg: Mon Dec :: []: ooh... really? -- TestEosPeg: Mon Dec :: []: ooh... really? -- TestEosPeg: Mon Dec :: []: ooh... really? -- TestEosPeg: Mon Dec :: []: sure. just try! -- TestEosPeg: Mon Dec :: []: sure. just try! -- TestEosPeg: Mon Dec :: = (0x0) <FUNCALL : size=>
->

现在,讲一讲原理,和一些使用注意事项。

首先是关于那个字符串指针的问题。有人会犹豫,怎么就只存个指针,而不是个字符串呢?这个,需要理解下编译器和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是个不错的选择,有需要的不妨一读!

EOS -- 一种灵巧的系统运行跟踪模块的更多相关文章

  1. 操作系统篇-hello world(免系统运行程序)

     || 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.前言     今天起开始分享关于操作系统的相关知识,本人也是菜鸟一个,正处于学习阶段,这整个操作系统篇也是我边学习边总结的一些结果,希 ...

  2. [论文笔记] 一种Java遗留系统服务化切分和封装方法 (计算机学报, 2009)

    李翔,怀进鹏,曾晋,高鹏. 一种Java遗留系统服务化切分和封装方法. 计算机学报, 32(9), 2009, p1084-1815 (gs:5) 1. 本文研究从Java遗留系统中切分并封装出Web ...

  3. Linux三种关机/重启系统的命令

    Linux提供了三种关机/重启系统的命令:shutdown.halt和reboot.这三个命令在一般情况下只有 系统的超级用户(一般是指root)才可以执行.输入没有参数的shutdown命令,两分钟 ...

  4. Linux系统——运行级别

    学习之前先了解下Linux系统的运行级别和其原理,博主使用的是Linux系统中的Redhat9.0版本,之后的学习也是基于这个系统版本. Linux系统的7个运行级别(runlevel) 运行级别0: ...

  5. SRE之道:创造软件系统来维护系统运行

    引言:本文作者Ben Treynor Sloss,Google 运维团队的高级副总裁,SRE 名称的发明者,在这里提供了他对SRE 的定义.  本文选自<SRE:Google运维解密>. ...

  6. 系统运行缓慢,CPU 100%,以及Full GC次数过多问题的排查思路

    前言 处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题.当然,这些问题的最终导致的直观现象就是系统运行缓慢,并且有大量的报警. 本文主要针对系统运 ...

  7. 如何弄清Linux系统运行何种系统管理程序

    如何弄清Linux系统运行何种系统管理程序 虽然我们经常听到系统管理器System Manager这词,但很少有人深究其确切意义.现在我们将向你展示其区别. 我会尽自己所能来解释清楚一切.我们大多都知 ...

  8. Linux系统运行级与启动机制剖析

    原文作者:技术成就梦想 原文链接:http://ixdba.blog.51cto.com/2895551/533740 一 系统运行级windows系统有安全运行模式和正常运行模式,这是两个不同的运行 ...

  9. Linux系统运行级别配置

    Linux的运行级别 Linux的运行级别有七种,可以通过查看/etc/inittab文件进行了解: Level0:系统停机状态,默认系统运行级别不能设置为0,否则系统不能正常启动: Level1:单 ...

随机推荐

  1. Python导入模块出现“no module named **”可能是这样的原因

    因为Python中其他文件中海油和这些模块一样的取名,这个时候Python就找不到到底是哪个文件了,所以只需要将其中一个文件的名字修改成不一样的就可以了.

  2. [转载]Matlab之静态文本多行输出

    转载文章,原文链接:Matlab中的静态文本框中显示多行内容 有时候,我们在GUI中利用静态文本框显示程序的结果,但是结果很长,一行未必可以显示的开,而静态文本框不像edit或listbox那样通过滚 ...

  3. Oracle数据访问组件ODAC的安装方法:

    Oracle数据访问组件ODAC(Oracle Data Access Components)顾名思义就是用来访问Oracle数据库的小程序.我们可以编程调用这些组件来实现在没有安装Oracle数据库 ...

  4. Java Calendar 注意事项

    Java JDK 提供了java.util.Calendar来处理日期和时间.Calendar是一个抽象类,是所有日历的模板,因此我们可以继承Calendar来实现其他的历法(比如阴历). Java提 ...

  5. OO基本原则

    1. 单一职责原则(SRP)     一个类应该最多只能有一个因素能够给导致其变化,类中的方法应该都是相关性很高的,即"高内聚"   2. 开放-封闭原则(OC)      - 扩 ...

  6. eclipse部署web项目至本地的tomcat但在webapps中找不到

    一.发现问题 在eclipse中新建Dynamic Web Project,配置好本地的tomcat并写好代码后选择Run on Server,但运行后发现在tomcat的安装目录下的webapps并 ...

  7. python 自动发邮件 Errno61 Connection refused

    此问题是在mac机器上遇到 之前在windows平台运行ok的脚本在mac上报错 后来查了半天 发现是网络接入不对 切换下网络后问题就解决了

  8. NC 单据保存时间过长,判断数据库锁表解决办法

    SELECT s.sid, s.serial# FROM gv$locked_object l, dba_objects o, gv$session s WHERE l.object_id = o.o ...

  9. 第五回. $e$ 的引入

    假如你有 $1$ 块钱, 存银行, 利率为 $100\%$, 那么一年后本息和为$$1+1=2.$$ 如果你换种存法, 存半年, 把本息和取出来, 再存半年, 那么一年后本息和为$$\left(1+\ ...

  10. opencv 处女作

    显示一幅图:主要是运用功能:imread namedWindow imshowimread:从字面意思我们就可以看懂,用来读取图片的:namedWindow:显然,我们也可以看到这是用来命名窗口名称的 ...