DPDK — RTE_LOG 日誌模塊

目錄

DPDK 的日誌系統

在版本較新的 DPDK 中引入了動態類型日誌系統。此外,除了原來支持的全局日誌輸出,也支持了針對單獨某個模塊的日誌輸出。本文以 18.05 版本進行闡述。

RTE_LOG 宏

DPDK 封裝好了 RTE_LOG 宏供開發 App 使用,如下:

// x86_64-native-linuxapp-gcc/include/rte_log.h

/**
 * Generates a log message.
 *
 * The RTE_LOG() is a helper that prefixes the string with the log level
 * and type, and call rte_log().
 *
 * @param l
 *   Log level. A value between EMERG (1) and DEBUG (8). The short name is
 *   expanded by the macro, so it cannot be an integer value.
 * @param t
 *   The log type, for example, EAL. The short name is expanded by the
 *   macro, so it cannot be an integer value.
 * @param ...
 *   The fmt string, as in printf(3), followed by the variable arguments
 *   required by the format.
 * @return
 *   - 0: Success.
 *   - Negative on error.
 */
#define RTE_LOG(l, t, ...)                    \
     rte_log(RTE_LOG_ ## l,                    \
         RTE_LOGTYPE_ ## t, # t ": " __VA_ARGS__)

可見,RTE_LOG 宏包含定義爲可變長參數,其中包括兩個非常關鍵的必選參數:

  1. 日誌等級
  2. 日誌類型

日誌等級的定義如下:

/* Can't use 0, as it gives compiler warnings */
#define RTE_LOG_EMERG    1U  /**< System is unusable.               */
#define RTE_LOG_ALERT    2U  /**< Action must be taken immediately. */
#define RTE_LOG_CRIT     3U  /**< Critical conditions.              */
#define RTE_LOG_ERR      4U  /**< Error conditions.                 */
#define RTE_LOG_WARNING  5U  /**< Warning conditions.               */
#define RTE_LOG_NOTICE   6U  /**< Normal but significant condition. */
#define RTE_LOG_INFO     7U  /**< Informational.                    */
#define RTE_LOG_DEBUG    8U  /**< Debug-level messages.             */

日誌類型定義如下:

/* SDK log type */                                                         
#define RTE_LOGTYPE_EAL        0 /**< Log related to eal. */               
#define RTE_LOGTYPE_MALLOC     1 /**< Log related to malloc. */            
#define RTE_LOGTYPE_RING       2 /**< Log related to ring. */              
#define RTE_LOGTYPE_MEMPOOL    3 /**< Log related to mempool. */           
#define RTE_LOGTYPE_TIMER      4 /**< Log related to timers. */            
#define RTE_LOGTYPE_PMD        5 /**< Log related to poll mode driver. */  
#define RTE_LOGTYPE_HASH       6 /**< Log related to hash table. */        
#define RTE_LOGTYPE_LPM        7 /**< Log related to LPM. */               
#define RTE_LOGTYPE_KNI        8 /**< Log related to KNI. */               
#define RTE_LOGTYPE_ACL        9 /**< Log related to ACL. */               
#define RTE_LOGTYPE_POWER     10 /**< Log related to power. */             
#define RTE_LOGTYPE_METER     11 /**< Log related to QoS meter. */         
#define RTE_LOGTYPE_SCHED     12 /**< Log related to QoS port scheduler. */
#define RTE_LOGTYPE_PORT      13 /**< Log related to port. */              
#define RTE_LOGTYPE_TABLE     14 /**< Log related to table. */             
#define RTE_LOGTYPE_PIPELINE  15 /**< Log related to pipeline. */          
#define RTE_LOGTYPE_MBUF      16 /**< Log related to mbuf. */              
#define RTE_LOGTYPE_CRYPTODEV 17 /**< Log related to cryptodev. */         
#define RTE_LOGTYPE_EFD       18 /**< Log related to EFD. */               
#define RTE_LOGTYPE_EVENTDEV  19 /**< Log related to eventdev. */          
#define RTE_LOGTYPE_GSO       20 /**< Log related to GSO. */ 

/* these log types can be used in an application */
#define RTE_LOGTYPE_USER1     24 /**< User-defined log type 1. */
#define RTE_LOGTYPE_USER2     25 /**< User-defined log type 2. */
#define RTE_LOGTYPE_USER3     26 /**< User-defined log type 3. */
#define RTE_LOGTYPE_USER4     27 /**< User-defined log type 4. */
#define RTE_LOGTYPE_USER5     28 /**< User-defined log type 5. */
#define RTE_LOGTYPE_USER6     29 /**< User-defined log type 6. */
#define RTE_LOGTYPE_USER7     30 /**< User-defined log type 7. */
#define RTE_LOGTYPE_USER8     31 /**< User-defined log type 8. */

/** First identifier for extended logs */
#define RTE_LOGTYPE_FIRST_EXT_ID 32 

DPDK App 使用 RTE_LOG 的簡單示例如下:

RTE_LOG(INFO, EAL, "Just a test.\n");

但通常會爲 RTE_LOG 在封裝一層:


#define DPDK_APP_LOG(level,...) RTE_LOG(level, DPDK_APP, "["#level"] "__VA_ARGS__)

rte_log 和 rte_vlog 函數

int rte_log(uint32_t level, uint32_t logtype, const char *format, ...)
{
    va_list ap;
    int ret;    
    va_start(ap, format);
    ret = rte_vlog(level, logtype, format, ap);
    va_end(ap);
    return ret;
}       

rte_log 函數很簡單,就是一個可變長函數的實現,直接調用了 rte_vlog 函數。

int rte_vlog(uint32_t level, uint32_t logtype, const char *format, va_list ap)
{ 
    int ret;
    FILE *f = rte_logs.file;  /* 日誌輸出到全局變量指向的終端或文件 */

	/* 如果沒有設置全局變量,則打印到默認終端 */
    if (f == NULL) {
		/* default_log_stream 的設置在 eal_log_set_default 函數中,是在對日誌進行初始化時設置的 */
        f = default_log_stream; 
        /* 如果默認輸出的地方也沒設置,則直接輸出到 stderr */
        if (f == NULL) { 
            f = stderr; 
        } 
    }
	/* 如果當前的打印等級超過設置的全局等級,直接退出 */
    if (level > rte_logs.level) 
        return 0; 

	/* 如果當前日誌類型超出範圍,直接退出並返回 -1,表示存在問題 */
    if (logtype >= rte_logs.dynamic_types_len)
        return -1;
        
	/* 如果當前日誌等級超出當前模塊設置的等級,直接退出 */
    if (level > rte_logs.dynamic_types[logtype].loglevel) 
        return 0; 

    /* save loglevel and logtype in a global per-lcore variable */        
    /* 記錄當前的打印等級,目前在調用 syslog 函數時使用 */
    RTE_PER_LCORE(log_cur_msg).loglevel = level; 
    RTE_PER_LCORE(log_cur_msg).logtype = logtype;

    ret = vfprintf(f, format, ap); 
    fflush(f); 
    return ret;
} 

日誌模塊初始化

DPDK 日誌模塊的初始化分爲兩部分:

  1. 第一階段(日誌模塊前期初始化):是在 DPDK 模塊被加載時執行的初始化,這部分初始化使得日誌模塊在 DPDK 的其他模塊初始化過程中可以使用。
  2. 第二階段(日誌模塊後期初始化):在 DPDK 環境抽象層初始化(rte_eal_init)時執行,用來設置日誌模塊的輸出。

第一階段初始化

rte_logs 結構體類型:

struct rte_logs {
    uint32_t type;  /**< 已啓用的日誌的位字段 */
    uint32_t level; /**< 日誌級別 */
    FILE *file;     /**< 由 rte_openlog_stream 指定的輸出文件,或者爲 NULL */
    size_t dynamic_types_len; /**< 動態日誌類型數量 */
    struct rte_log_dynamic_type *dynamic_types;  /**< 存儲每種動態類型的名字與級別 */
};  
  • type:表示啓用的 logs 模塊的位掩碼。在 18.05 版本中,這個變量還沒有用到。
  • level:表示全局日誌級別,只輸出不大於該級別的日誌。
  • file:表示日誌輸出的地方,可以是終端,也可以是指定的文件。
  • dynamic_types_len:表示動態日誌類型的數量,每添加一個類型,該變量增加 1。
  • dynamic_types:指向表示動態日誌類型的結構體,保存的日誌類型的名字的設置的等級,定義如下:
struct rte_log_dynamic_type {
    const char *name;		/* 日誌類型的名字 */
    uint32_t loglevel;		/* 該日誌類型的日誌級別,日誌模塊只輸出不大於該級別,不大於全局日誌級別的日誌 */
};      

定義 rte_logs 全局日誌變量:

/* global log structure */
struct rte_logs rte_logs = {
    .type = ~0, 			/* 默認啓用全部日誌類型 */
    .level = RTE_LOG_DEBUG, /* 默認級別爲 DEBUG,即輸出所有日誌 */
    .file = NULL,
};

第一階段的初始化

RTE_INIT_PRIO(rte_log_init, LOG);

static void rte_log_init(void)
{
    uint32_t i;

	/* 設置 rte_logs.level */
    rte_log_set_global_level(RTE_LOG_DEBUG);

	 /* 爲所有日誌類型分配內存,默認共 RTE_LOGTYPE_FIRST_EXT_ID(爲 32)個類型 */
    rte_logs.dynamic_types = calloc(RTE_LOGTYPE_FIRST_EXT_ID,
        sizeof(struct rte_log_dynamic_type));
        
    if (rte_logs.dynamic_types == NULL)
        return;
    /* register legacy log types */
    for (i = 0; i < RTE_DIM(logtype_strings); i++)
    	/* 註冊 logtype_strings[] 中定義的所有日誌類型 */
        __rte_log_register(logtype_strings[i].logtype,
                logtype_strings[i].log_id);    
    rte_logs.dynamic_types_len = RTE_LOGTYPE_FIRST_EXT_ID;
}   

logtype_strings[] 數組的定義如下:

struct logtype {        
    uint32_t log_id;    
    const char *logtype;
}; 

static const struct logtype logtype_strings[] = {
    {RTE_LOGTYPE_EAL,        "lib.eal"},
    {RTE_LOGTYPE_MALLOC,     "lib.malloc"}, 
    {RTE_LOGTYPE_RING,       "lib.ring"}, 
    {RTE_LOGTYPE_MEMPOOL,    "lib.mempool"},
……
    {RTE_LOGTYPE_EVENTDEV,   "lib.eventdev"},
    {RTE_LOGTYPE_GSO,        "lib.gso"},     
    {RTE_LOGTYPE_USER1,      "user1"},       
    {RTE_LOGTYPE_USER2,      "user2"},       
    {RTE_LOGTYPE_USER3,      "user3"},       
    {RTE_LOGTYPE_USER4,      "user4"},       
    {RTE_LOGTYPE_USER5,      "user5"},       
    {RTE_LOGTYPE_USER6,      "user6"},       
    {RTE_LOGTYPE_USER7,      "user7"},       
    {RTE_LOGTYPE_USER8,      "user8"}        
}; 

__rte_log_register 函數註冊日誌類型,將表示日誌類型的 id 與類型名保存到 rte_logs.dynamic_types 中,返回該類型在 rte_logs.dynamic_types 中的索引,因此這些日誌類型,如 RTE_LOGTYPE_EAL 等,同時也成爲了該類型在 rte_logs.dynamic_types 中的索引:

static int __rte_log_register(const char *name, int id)           
{   
    char *dup_name = strdup(name);
    if (dup_name == NULL)                              
        return -ENOMEM;
    rte_logs.dynamic_types[id].name = dup_name;
    rte_logs.dynamic_types[id].loglevel = RTE_LOG_INFO;
    return id;
}

最後設置日誌類型的數量:

rte_logs.dynamic_types_len = RTE_LOGTYPE_FIRST_EXT_ID;

可以看出,第一階段的日誌模塊初始化是爲了完成 DPDK 本身的日誌配置,爲後續加載 DPDK 其他模塊的時候提供日誌支持

第二階段初始化

日誌模塊的後期初始化是通過在 rte_eal_init 函數中調用 rte_eal_log_init 函數來完成,該函數定義如下:

int rte_eal_log_init(const char *id, int facility) 
{
    FILE *log_stream; 
    log_stream = fopencookie(NULL, "w+", console_log_func);
    if (log_stream == NULL) 
        return -1;
    openlog(id, LOG_NDELAY | LOG_PID, facility);  
    eal_log_set_default(log_stream); 
    return 0;
}

首先使用 fopencook 函數創建了一個標準輸入輸出流:

extern FILE *fopencookie (void *__restrict __magic_cookie,
              const char *__restrict __modes,
              _IO_cookie_io_functions_t __io_funcs) __THROW __wur;
  • __magic_cookie:是一種自定義的數據結構,用於和後面的 __io_funcs 配合使用,可以爲 NULL。
  • __modes:表示打開方式,和 fopen 相同,包括:r、w、a、r+、w+、a+ 等。
  • __io_funcs:是由四個函數指針(read、write、seek、close)組成的函數集,需要用戶實現這四個函數指針。

console_log_func 函數的定義如下:

static cookie_io_functions_t console_log_func = {
    .write = console_log_write,
};

由於日誌模塊只需要輸出字符串,因此變量只實現了寫函數 console_log_write 函數,定義如下:

static ssize_t
console_log_write(__attribute__((unused)) void *c, const char *buf, size_t size)
{ 
    char copybuf[BUFSIZ + 1]; 
    ssize_t ret;
    uint32_t loglevel;
    /* 將日誌內容輸出到 stdout 標準輸出 */
    ret = fwrite(buf, 1, size, stdout);
    fflush(stdout);

    /* truncate message if too big (should not happen) */
	/* syslog 日誌輸出,每次最多輸出 BUFSIZ 個字符 */
    if (size > BUFSIZ)
        size = BUFSIZ;

    /* Syslog error levels are from 0 to 7, so subtract 1 to convert */
	/* 計算 syslog 日誌輸出級別。由於級別的定義是從 1 開始定義的,這裏需要減去 1 */
    loglevel = rte_log_cur_msg_loglevel() - 1;
    memcpy(copybuf, buf, size);
    copybuf[size] = '\0';

    /* 寫入 syslog 日誌 */
    syslog(loglevel, "%s", copybuf);

    return ret;
} 

syslog 日誌初始化:

openlog(id, LOG_NDELAY | LOG_PID, facility);

設置 DPDK 日誌模塊的輸出對象:

eal_log_set_default(log_stream);

將剛剛創建並初始化的 log_stream,賦值給 default_log_stream。根據前文 rte_vlog 函數的定義,在輸出日誌時會使用。也就是說,默認情況下,DPDK 的標準日誌模塊會向標準輸出 stdout 輸出日誌信息,同時會向 syslog 中輸出日誌信息。

可見,DPDK 日誌模塊第二階段的初始化主要關注日誌的使用和打印方式,實際上這些主要都交由 DPDK App 來進行設置

註冊新的日誌類型

除了上述 DPDK 默認的日誌類型外,DPDK 的日誌系統也支持爲 DPDK App 註冊新的日誌類型,也就是所謂的動態熱值類型系統。

  1. 添加新的日誌類型,通過 rte_log_register 函數,可以向系統中添加新的日誌類型。該函數定義如下:
/* register an extended log type */                            
int rte_log_register(const char *name)
{
    struct rte_log_dynamic_type *new_dynamic_types;            
    int id, ret; 

	/* 在 rte_logs.dynamic_types 中查詢是否已經註冊同名日誌類型,如果已經註冊,則直接返回該類型的索引 */
    id = rte_log_lookup(name); 
    if (id >= 0) 
        return id; 

	/* 爲新類型重新分配內存,爲 rte_logs.dynamic_types 增加一個類型的空間,並將其指向新類型 */
    new_dynamic_types = realloc(rte_logs.dynamic_types,        
        sizeof(struct rte_log_dynamic_type) * (rte_logs.dynamic_types_len + 1));                     
    if (new_dynamic_types == NULL)
        return -ENOMEM; 
    rte_logs.dynamic_types = new_dynamic_types; 
    
	/* 將新類型註冊到 rte_logs.dynamic_types 中,索引就是 rte_logs.dynamic_types 空間中新分配的位置 */
    ret = __rte_log_register(name, rte_logs.dynamic_types_len);
    if (ret < 0) 
        return ret; 
        
	/* 類型總數增加 */
    rte_logs.dynamic_types_len++; 
    return ret; 
} 
  1. 設置/獲取日誌的打印級別,包含了全局日誌打印級別的獲取與設置函數,以及指定模塊的日誌打印級別的獲取與設置函數。定義如下:
/* Set global log level */   
/* 設置全局日誌打印級別 */                     
void rte_log_set_global_level(uint32_t level) 
{
    rte_logs.level = (uint32_t)level;             
} 

/* Get global log level */  
/* 獲取全局日誌打印級別 */                      
uint32_t rte_log_get_global_level(void)                    
{  
    return rte_logs.level;                        
}  

/* 獲取指定日誌類型的打印級別 */
int rte_log_get_level(uint32_t type)                  
{ 
    if (type >= rte_logs.dynamic_types_len)       
        return -1;                                
    return rte_logs.dynamic_types[type].loglevel; 
} 

/* 設置指定日誌類型的打印級別 */                                               
int rte_log_set_level(uint32_t type, uint32_t level)  
{  
    if (type >= rte_logs.dynamic_types_len)       
        return -1;                                
    if (level > RTE_LOG_DEBUG)                    
        return -1;                                
    rte_logs.dynamic_types[type].loglevel = level;
    return 0; 
}
  1. 應用舉例,以 testpmd 這個程序爲例,在 main 函數就註冊了一個新的日誌類型:
testpmd_logtype = rte_log_register("testpmd");

返回值 testpmd_logtype 同時也表示了該類型在 rte_logs.dynamic_types 中的索引。之後設置該類型的打印級別爲 DEBUG,打印所有該類型的日誌:

rte_log_set_level(testpmd_logtype, RTE_LOG_DEBUG);

同時,在 testpmd 示例的頭文件中,還對日誌函數進行了對應的封裝:

#define TESTPMD_LOG(level, fmt, args...) \
    rte_log(RTE_LOG_ ## level, testpmd_logtype, "testpmd: " fmt, ## args)

此後,就可以在 testpmd 程序中使用 TESTPMD_LOG 宏進行日誌打印了。

複用現有日誌類型

除了添加新的日誌類型,也可以複用現有的日誌類型,以 exception_path 這個示例程序爲例。在 ip_fragmentation/main.c 文件中,有如下定義:

#define RTE_LOGTYPE_IP_FRAG RTE_LOGTYPE_USER1

其中,RTE_LOGTYPE_USER1 到 RTE_LOGTYPE_USER8 這 8 個類型,就是爲用戶自定義準備的,所以可以直接使用。在使用時,調用 RTE_LOG 宏時直接使用 IP_FRAG 即可:

RTE_LOG(INFO, IP_FRAG, "XXXXXX");

參考文檔

https://www.sunxidong.com/36.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章