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

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__0809 返回首页
实付 49.00元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值