热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SPDKTraceLog用法简介

今天我们给大家介绍SPDKTraceLog在DebugFreebuild下针对特定模块的使用。温馨提示:由于部分函数较长,建议使用电脑端或者手机横屏阅读

今天我们给大家介绍SPDK Trace Log在Debug/Free build下针对特定模块的使用。

温馨提示:由于部分函数较长,建议使用电脑端或者手机横屏阅读~

什么是日志

日志,又称为Log,是我们开发人员的一款利器。我们通常在程序代码中插入一些特殊的输出代码,将程序当前的运行状态按需输出,以便于在无人值守的情况下记录信息,在事后对程序的处理过程进行分析。不管是在调试还是测试的阶段,日志都是我们的得力助手。

一个完善的日志系统不只是简单地在控制台输出,还具有以下几个特征:

  • 使用一种方式就支持输出到多个目标,例如:控制台,文本文件,数据库等。

  • 允许控制输出的级别,过滤输出的内容,而不需要大幅度修改日志程序。

  • 使用简单,可以使用简单的语法来记录日志。

Linux内核中的日志系统函数和SPDK的封装

我们先来了解一下linux内核的三个系统函数openlogsyslogcloselog,这是一套系统日志写入接口。还有一个vsyslog,和syslog功能一样,只是参数格式不同。

2.1. openlog

openlog函数用来打开一个到系统日志记录程序的连接,打开之后就可以用syslog或vsyslog函数向系统日志里添加信息了。而closelog函数就是用来关闭此连接的。

通常来说,openlog需要在模块最开始指定,即限定了这个模块内都是一个facility的日志,SPDK里指定的是 LOG_LOCAL7。定义在/lib/event/app.c中

1.  void spdk_log_open(logfunc *logf)  

2.  {  

3.      if (logf) {  

4.          g_log = logf;  

5.      } else {  

6.          openlog("spdk", LOG_PID, LOG_LOCAL7);  

7.      }  

8.  }  

参数说明

LOG_PID,Include PID with each message,更多的是方便调试。

LOG_LOCAL0~LOG_LOCAL7, 为本地使用保留。

2.2. syslog

syslog 是Linux系统默认的日志守护进程。任何希望生成日志信息的程序都可以向 syslog 接口呼叫生成该信息,把日志消息发给系统程序syslogd去记录。

如果我们的程序要使用系统日志功能,只需要在程序启动时使用openlog函数来连接syslogd程序,后面随时用syslog函数写日志就行了。

SPDK代码里是这样使用的:

1. void  spdk_vlog(enum spdk_log_level level, const char *file, const int line, const char *func,  const char *format, va_list ap)  

2. {  

3.      ……

4.      if (level <= g_spdk_log_level) {  

5.          syslog(severity, "%s:%4d:%s: *%s*: %s", file, line, func, spdk_level_names[level], buf);  

6.      }  

7. }  

日志打印效果看上去就是这样的,

00:10:27.002  [2021-12-23 04:27:19.499391] nvme_rdma.c:1778:nvme_rdma_ctrlr_create_qpair: *DEBUG*: RDMA requests

同时,syslog为每个事件赋予几个不同的优先级:

  • LOG_ERR,错误 信息

  • LOG_WARNING,警告信息 

  • LOG_NOTICE,不是错误情况,但是可能需要处理 

  • LOG_INFO,一般的打印信息 

  • LOG_DEBUG,调试信息 

SPDK也定义了类似的6个日志事件优先等级:

1.  enum spdk_log_level {  

2.  SPDK_LOG_DISABLED = -1,/** All messages will be suppressed. */

3.  SPDK_LOG_ERROR,  

4.  SPDK_LOG_WARN,  

5.  SPDK_LOG_NOTICE,  

6.  SPDK_LOG_INFO,  

7.  SPDK_LOG_DEBUG,  

8.  }; 

2.3. closelog

最后closelog函数用来关闭上面的连接。SPDK代码里的调用:

1.  void spdk_log_close(void)  

2.  {  

3.      if (!g_log) {  

4.          closelog();  

5.      }  

6.  }  

SPDK log相关的RPC命令

3.1 基本的log命令

SPDK通过RPC命令,提供了丰富的log访问存取接口,和log相关的命令有下列这些,

    log_set_flag (set_log_flag)

                        set log flag        # 设置日志标记

    log_clear_flag (clear_log_flag)

                        clear log flag          # 清除日志标记

    log_get_flags (get_log_flags)

                        get log flags       # 获取全部模块日志标记

    log_set_level (set_log_level)

                        set log level           # 设置日志事件优先级别

    log_get_level (get_log_level)

                        get log level           # 获取当前日志事件优先级别

    log_set_print_level (set_log_print_level)

                        set log print level # 设置日志终端打印级别

    log_get_print_level (get_log_print_level)

                        get log print level # 获取日志终端打印级别

    log_enable_timestamps   # 显示系统时间戳,例如[2021-12-23 10:18:12.642789]

                        -d, --disable  Disable timestamps, 默认值

                        -e, --enable   Enable timestamps

下面举例简单介绍一下这些命令的使用方法。

3.1.1 4个log_level命令

在SPDK里,使用RPC命令的前提是开启target server,这里以nvmf_tgt为例,

./build/bin/nvmf_tgt --json bdev_nvme.json   # bdev_nvme.json内容请见附件

                                                                            # 另开一个终端窗口

./scripts/rpc.py log_set_level "NOTICE"      # 这里最低只能设置"ERROR"

./scripts/rpc.py log_get_level               # 显示当前log等级

"NOTICE"

./scripts/rpc.py log_set_print_level "INFO"  # 设置当前log打印等级

./scripts/rpc.py log_set_print_level         # 显示当前log打印等级

"INFO"

log_set_ print_level和log_set_level略有区别。前者设置打印级别,后者设置记录级别,记录了但不一定要在终端打印出来。从下面这段简化的spdk_vlog函数代码示例可以看出来。

1. void  spdk_vlog(enum spdk_log_level level, const char *file, const int line, const char *func,  const char *format, va_list ap)  

2. {  

3.      if (level <= g_spdk_log_print_level) {  

4.          fprintf(stderr, "%s%s:%4d:%s: *%s*: %s", timestamp, file, line, func, spdk_level_names[level], buf); 

5.      }  

6.    

7.      if (level <= g_spdk_log_level) {  

8.          syslog(severity, "%s:%4d:%s: *%s*: %s", file, line, func, spdk_level_names[level], buf);  

9.      }  

10.}  

接下来,产生一个ERROR的打印。这里参数-c Nmmm理应是Nvme0,故意输错名字,target端会产生错误打印,

./scripts/rpc.py bdev_nvme_get_controller_health_info -c Nmmm

# target终端窗口

bdev_nvme_rpc.c:1537:rpc_bdev_nvme_get_controller_health_info: *ERROR*: nvme ctrlr name &#39;Nmmm&#39; does not exist

3.1.2 时间戳开关

记录每次被更新时,系统都会自动维护时间戳,我们可以选择显示或者关闭显示时间戳。

./scripts/rpc.py log_enable_timestamps -d    # 关闭时间戳

./scripts/rpc.py log_enable_timestamps -e    # 开启时间戳

./scripts/rpc.py bdev_nvme_get_controller_health_info -c Nmmm

[2021-12-23 10:18:12.642789] bdev_nvme_rpc.c:1537:rpc_bdev_nvme_get_controller_health_info: *ERROR*: nvme ctrlr name &#39;Nmmm&#39; does not exist

3.1.3 debug开关

如果要系统产生某个指定模块更加详细的log,可以打开debug开关。需要在开启target时,使用-L参数,加模块名,all表示全部模块。

./build/bin/nvmf_tgt --json bdev_nvme.json -L nvme   # 指定某个模块或all

./scripts/rpc.py log_get_print_level

"DEBUG"

请注意,这里前提是编译SPDK代码的时候要带上debug参数,如下所示:

./configure --enable-debug

3.2 针对NVMe模块的使用

上面介绍了基本的log命令用法, 接下来我们拿NVMe作为例子,针对特定模块,设置log flag。也就是说,让target服务端只打印NVMe bdev相关的log。

首先通过RPC log_set_flag命令选择特定的模块,

./scripts/rpc.py log_set_flag "nvme"

./scripts/rpc.py log_get_flags  # 查看全部模块标记

  "log": false,

  "log_rpc": false,

  "lvol": false,

  "lvol_rpc": false,

  "nbd": false,

  "notify_rpc": false,

  "nvme": true,                             # 会显示true,已经设置好

清除log flag是用log_clear_flag。

./scripts/rpc.py log_clear_flag "nvme"

下面举个完整的例子。

首先启动nvmf target服务端应用程序。

./build/bin/nvmf_tgt

然后创建名为Nvme0的bdev,然后删除。

./scripts/rpc.py bdev_nvme_attach_controller -b Nvme0 -t pcie -a 0000:5f:00.0

./scripts/rpc.py bdev_nvme_detach_controller Nvme0

我们观察,target服务端窗口都没有任何新增log显示。这时,通过RPC log_set_flag命令,设置“nvme”log标志,再重新创建和删除bdev。

./scripts/rpc.py log_set_flag "nvme" 

./scripts/rpc.py bdev_nvme_attach_controller -b Nvme0 -t pcie -a 0000:5f:00.0

./scripts/rpc.py bdev_nvme_detach_controller Nvme0

再观察target服务端窗口,则会出现刚刚加载以及卸载Nvme0的log。

[2021-12-23 11:14:56.655651] nvme_pcie_common.c: 158:nvme_pcie_qpair_construct: *INFO*: max_completions_cap = 8 num_trackers = 24

[2021-12-23 11:14:56.677745] nvme_qpair.c: 251:nvme_admin_qpair_print_command: *NOTICE*: IDENTIFY (06) qid:0 cid:23 nsid:0 cdw10:00000001 cdw11:00000000 PRP1 0x16b6a5c000 PRP2 0x0

……

[2021-12-23 11:15:16.683121] nvme_qpair.c: 456:spdk_nvme_print_completion: *NOTICE*: ABORTED - SQ DELETION (00/08) qid:0 cid:22 cdw0:0 sqhd:0000 p:0 m:0 dnr:0

3.3 针对NBD模块的使用

我们再拿NBD(Network Block Device)作为例子,设置log flag,让target服务端只打印NBD bdev相关的log。

首先通过RPC命令设置log_set_flag,

./scripts/rpc.py log_set_flag "nbd"

./scripts/rpc.py log_get_flags

  "log": false,

  "log_rpc": false,

  "lvol": false,

  "lvol_rpc": false,

  "nbd": true,                          # 会显示true,已经设置好

要测试NBD设备,需要准备一些前提条件,

modprobe nbd                  # 加载nbd模块(如果没有,需要下载)
lsmod | grep nbd              # 查看nbd模块是否加载

接下来看下对比效果。启动nvmf target服务端应用程序。

./build/bin/nvmf_tgt

创建NBD的bdev。

./scripts/rpc.py bdev_malloc_create 256 512

./scripts/rpc.py nbd_start_disk Malloc0 /dev/nbd0

./scripts/rpc.py nbd_stop_disk /dev/nbd0

我们观察,target服务端窗口都没有任何新增log显示。这时通过RPC命令,设置“nbd”log标志后,再重新创建NBD。

./scripts/rpc.py log_set_flag "nbd"

./scripts/rpc.py nbd_start_disk Malloc0 /dev/nbd0

target服务端窗口,则会出现刚才加载NBD的log。

[2021-12-30 00:15:13.433639] nbd.c:1147:spdk_nbd_start: *INFO*: Enabling kernel access to bdev Malloc0 via /dev/nbd0

如何给自定义的第三方Bdev添加对应的Trace

4.1 注册flag

块设备模块对于SPDK来说,相当于传统操作系统中的设备驱动程序。该模块提供了一组函数指针,这些函数指针被调用以服务块设备I / O请求。SPDK提供了许多块设备模块,包括NVMe,RAM-disk和Ceph RBD。如果用户希望自己编写以与自定义硬件或现有存储软件堆栈进行交互,具体请参考https://spdk.io/doc/bdev_module.html。要添加新模块的log开关,需要额外注册新的模块的名字,本质上记录和打印log最终也是调用第二章提到的linux日志系统函数。本文假设用户已经编写好名为bdev_abc的第三方模块,现在添加对应的log trace flag。

首先在已写好的bdev_abc.c代码里注册log flag。

1. SPDK_LOG_REGISTER_COMPONENT(bdev_abc)  

这个宏定义调用了下面的函数,也就是一个入队的操作。

1. void spdk_log_register_flag(const char *name, struct spdk_log_flag *flag)  

2. {  

3.      ......  

4.      TAILQ_INSERT_TAIL(&g_log_flags, flag, tailq);  

5. }  

当新增flag时,向已有的g_log_flags链表队列插入新结点bdev_abc,如图所示。

2294d77ac7a825d1debe28d8088b35cd.png

图4-1:g_log_flags链表队列

说明:0/1是bool类型的flag开关。

4.2 打开/关闭flag

新添加模块的log flag开关默认是关闭的,需要的时候可以打开。这将使用到第三章提到的RPC命令,例如log_set_flag/ log_clear_flag。这里的set/clear flag的一套操作,都是在log_flag.c里完成的。它们用来控制输出或者不输出和该模块相关的log打印。

./scripts/rpc.py log_set_flag "bdev_abc"

./scripts/rpc.py log_get_flags

  "log": false,

  "log_rpc": false,

  "lvol": false,

  "lvol_rpc": false,

  "nbd": false,

  "notify_rpc": false,

  "bdev_abc": true,                # 会显示true,已经设置好

怎么知道当前SPDK app都支持哪些log flag模块呢?也就是说,现有队列包含哪些结点?这个可以通过app -h命令查看。

./build/bin/nvmf_tgt -h

……

-L, --logflag     enable log flag (all, accel, accel_ioat, aio, app_config, app_rpc, bdev, bdev_ftl, bdev_malloc, bdev_null, bdev_nvme, bdev_raid, bdev_raid0, blob, blob_rw, blobfs, blobfs_bdev, blobfs_bdev_rpc, blobfs_rw, ftl_core, ftl_init…

注:显示的结果,根据SPDK编译时./configure --with的开关,会略有不同。

4.3 查看log打印

当需要打印log的时候,我们可以在代码里使用下面四种封装函数宏定义:SPDK_NOTICELOG、SPDK_WARNLOG、SPDK_ERRLOG和SPDK_DEBUGLOG。其实质是通过封装的spdk_vlog函数,来调用linux内核的syslog和fprintf函数,具体请参考第3.1.1节的spdk_vlog。

用法举例:

1.  SPDK_NOTICELOG("Unable to perform reset, already in progress.\n"); 

2.  SPDK_WARNLOG("Specified command could not be aborted.\n"); 

3.  SPDK_ERRLOG("compare failed: rc = %d\n", rc); 

以上这些函数用法跟C语言的printf()一样。只有SPDK_DEBUGLOG,需要指定模块名,以便app -L的时候可以筛选该模块的log信息。

1. SPDK_DEBUGLOG(bdev_abc, "No action for abc controller timeout.\n"); 

再通过RPC命令打开该模块的debug开关。

./build/bin/nvmf_tgt -L bdev_abc

以上步骤完成后,我们就可以在运行日志中看到自己添加的模块产生的警告、错误和调试信息。是不是很简单,小伙伴们快去动手试试吧。

附件

bdev_nvme.json

1.  {  

2.    "subsystems": [  

3.      {  

4.        "subsystem": "bdev",  

5.        "config": [  

6.          {  

7.            "method": "bdev_nvme_attach_controller",  

8.            "params": {  

9.              "name": "Nvme0",  

10.             "trtype": "PCIe",  

11.             "traddr": "0000:5f:00.0",  

12.             "prchk_reftag": false,  

13.             "prchk_guard": false  

14.           }  

15.         }  

16.       ]  

17.     }  

18.   ]  

19. }  

参考资料

1.https://spdk.io/doc/jsonrpc.html

2.https://spdk.io/doc/bdev_module.html

230c28438feb6374c4fc5039e53a8ac0.png

转载须知

DPDK与SPDK开源社区

公众号文章转载声明

推荐阅读

Intel 助力移动云百万 IOPS 云硬盘,打造极速云存储体验

深入浅出Hyperscan出版啦!

SPDK Vhost 基于最新21.10发布版本的性能报告

DPDK Release 21.11

e5727967b13b5507f853db8a66c2141c.png

444f8e5213f2276203ae2deed4a60868.gif

点点“赞”“在看”,给我充点儿电吧~



推荐阅读
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文讨论了在数据库打开和关闭状态下,重新命名或移动数据文件和日志文件的情况。针对性能和维护原因,需要将数据库文件移动到不同的磁盘上或重新分配到新的磁盘上的情况,以及在操作系统级别移动或重命名数据文件但未在数据库层进行重命名导致报错的情况。通过三个方面进行讨论。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
author-avatar
qyfdxlwb
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有