• 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.
    ");
    

    但通常会为 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] = '';
    
        /* 写入 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

    相关阅读:

  • 相关阅读:
    leetcode-409
    leetcode-836
    leetcode-1160
    leetcode-面试题13
    leetcode-695
    go的一些小lib
    leetcode-300
    cookie
    php上传文件
    PHP 文件创建/写入
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13309170.html
Copyright © 2020-2023  润新知