EOS -- 一種靈巧的系統運行跟蹤模塊

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是個不錯的選擇,有須要的不妨一讀!

相關文章
相關標籤/搜索