EOS -- 一种灵巧的系统运行跟踪模块

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

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 -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     : [email protected] (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是个不错的选择,有需要的不妨一读!

时间: 2024-12-21 00:54:33

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

Linux查看系统运行信息的常用命令

top: 查看CPU和内存使用情况 在系统维护的过程中,随时可能有需要查看 CPU 使用率,并根据相应信息分析系统状况的需要.在 CentOS 中,可以通过 top 命令来查看 CPU 使用状况.运行 top 命令后,CPU 使用状态会以全屏的方式显示,并且会处在对话的模式 -- 用基于 top 的命令,可以控制显示方式等等.退出 top 的命令为 q (在 top 运行中敲 q 键一次). top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Window

Linux学习笔记(三):系统运行级与运行级的切换

1.Linux系统与其他的操作系统不同,它设有运行级别.该运行级指定操作系统所处的状态.Linux系统在任何时候都运行于某个运行级上,且在不同的运行级上运行的程序和服务都不同,所要完成的工作和所要达到的目的也都不同. 2.Linux(Red Hat 9.0)设置了7个不同的运行级,系统可以在这些运行级别之间进行切换以完成不同的工作. 3.接下来简单介绍7个系统运行级: (1).运行级0:关闭计算机. (2).运行级1:单用户模式. (3).运行级2:多用户模式(不带网络文件系统NFS支持功能).

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

引言:本文作者Ben Treynor Sloss,Google 运维团队的高级副总裁,SRE 名称的发明者,在这里提供了他对SRE 的定义. 本文选自<SRE:Google运维解密>. 大家都知道, 计算机软件系统离开人通常是无法自主运行的.那么,究竟应该如何去运维一个日趋复杂的大型分布式计算系统呢?雇佣系统管理员(sysadmin)运维复杂的计算机系统,是行业内一直以来的普遍做法.而Google 的解决之道是--SRE.  SRE 团队通过雇佣软件工程师,创造软件系统来维护系统运行以替代传统

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

  一.前言 今天起开始分享关于操作系统的相关知识,本人也是菜鸟一个,正处于学习阶段,这整个操作系统篇也是我边学习边总结的一些结果,希望能给正在学习或者有意向学习操作系统的童鞋带来帮助. 二.有关知识 在进入代码之前,先给大家普及一些硬件知识,如果你已经具备了这方面的知识,可以直接略过这部份. 1.计算机怎么启动操作系统的? 首先,我们思考一个问题,为什么一个硬盘安装系统之后打开计算机电源之后就能正常加载启动呢?这看起来似乎很智能,似乎计算机像活的一样会自动去硬盘中找系统代码并自行加载.其实不然

Linux Shell常用技巧(九) 系统运行进程

Linux Shell常用技巧(九) 系统运行进程 十九.  和系统运行进程相关的Shell命令:       1.  进程监控命令(ps):      要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而ps命令就是最基本同时也是非常强大的进程查看命令.使用该命令可以 确定有哪些进程正在运行和运行的状态.进程是否结束.进程有没有僵死.哪些进程占用了过多的资源等等.总之大部分信息都是可以通过执行该命令得到的.    ps命令存在很多的命令行选项和参数,然而我们最为常用

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

原文作者:技术成就梦想 原文链接:http://ixdba.blog.51cto.com/2895551/533740 一 系统运行级windows系统有安全运行模式和正常运行模式,这是两个不同的运行级,同样,linux也有系统运行级别,并且linux系统的运行级别更加灵活,更加多样化.在讲述运行级别前,先讲述下linux下的init程序,因为init程序直接和系统运行级别相关联, init程序是linux操作系统最主要的程序之一,是一个由系统内核启动的用户级进程,同时init进程也是所有其它系

aix 系统运行级别

系统启动后可以运行在不同的级别上.有时候为了进行系统维护或者为了运行某些特殊的程序,我们需要改变系统的运行级别.在本文中,你将了解到如何检测系统的运行级别.如何改变系统的运行级别等知识. 1.什么是系统的运行级别?系统的运行级别是一种软件设置.这种软件设置是用来控制在该设置下,只有被选择的一组进程才能存在.换句话说,系统运行在不同的运行级别上,那么系统中可以有不同的进程在运行.系统的运行级别包括以下这些级别中的某一个:0-9: 这10种运行级别代表着系统的10种设置.当我们把系统的运行级别从一级

3种关闭linux系统端口方法

下面总结三种关闭linux系统端口的方法 1.通过杀掉进程的方法来关闭端口 每个端口都有一个守护进程,kill掉这个守护进程就可以了 每个端口都是一个进程占用着, 第一步.用下面命令 netstat -anp |grep 端口 找出占用这个端口的进程, 第二步.用下面命令 kill   PID      杀掉就行了 2.通过开启关闭服务的方法来开启/关闭端口 因为每个端口都有对应的服务,因此要关闭端口只要关闭相应的服务就可以了. linux中开机自动启动的服务一般都存放在两个地方: /etc/i

五种寻找 &quot;Domain Admin&quot; 运行的进程的方法

前言 当计算机加入到域后,默认将"Domain Admin"组赋予了本地系统管理员的权限.也就是说,在计算机添加到域,成为域的成员主机的过程中,系统将会自动把"Domain Admin"域组添加到本地的Administrators组中.因此,只要是Domain Admin组的成员均可访问本地计算机,而且具备"完全控制"的权限. 因此对于渗透测试人员来说,把"Domain Admin"域组添加到本地的Administrators