• 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
    ", 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
    ", num, ptr->errstr, ptr->count,   ptr->function, ptr->line, zCTime(&ptr->when));
        }
        else
        {
          zTraceP("[%5d]: %s  %d   -- %s:%d  %s
    ", 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是个不错的选择,有需要的不妨一读!

  • 相关阅读:
    kubernetes实战(十六):k8s高可用集群平滑升级 v1.11.x 到v1.12.x
    kubernetes实战(十四):k8s持久化部署gitlab集成openLDAP登录
    kubernetes实战(十三):k8s使用helm持久化部署harbor集成openLDAP登录
    kubernetes实战(十二):k8s使用helm持久化部署redmine集成openLDAP
    kubernetes实战(十一):k8s使用openLDAP统一认证
    Django数据库连接Mysql配置
    HmailServer 无法发信 端口25连接失败 ConnectFail not possible to connect 请看这里
    C++ Opencv安装教程 超详细图解 Vs2015/2017/2019(C++)绑定 VisualStduio
    Python Ftp Ftplib dir()方法 返回值问题
    Collections 初识
  • 原文地址:https://www.cnblogs.com/hhao020/p/5026536.html
Copyright © 2020-2023  润新知