温馨提示
✦
由于英文函数名较长,为了获得更佳的阅读体验,建议在手机横屏或电脑网页阅读~
✦
✦
一、背景介绍
2019年3月,SPDK社区首次实现了Delay Bdev,如这个Bdev的名称,主要用来模拟IO的Delay,并在SPDK 19. 07版本中正式发布。本次发布添加了新的bdev 模块“延迟(Delay)”,delay_bdev基于base_bdev,用于模拟驱动设备的延迟,此模块更多用于测试和调试不同IO情况来使用。可以使用新的RPC命令‘bdev_delay_create’来创建delay_bdev。
该RPC测参数包括base_bdev的名称,读写操作的平均延迟和读写操作的P99延迟。平均延迟即性能测试工具比如fio提交到驱动程序的所有I/O的平均延迟。P99 延迟指的是所有I/O中最慢的1%的I/O的延迟。对于具有显著延迟的底层驱动程序来说,这个延迟是一个很好的附加。如果试图在nvme驱动程序或aio设备上实现人工延迟,应该考虑使用Delay Bdev。
二、SPDK为什么要使用Delay Bdev模块
Delay_bdev模块是基于底层的base_bdev之上预先定义的延迟。从而在SPDK应用程序的功能性测试及扩展性测试期间来模拟设备的延迟。例如,为了模拟处理I/O时驱动程序的时延的影响,我们可以基于NULL bdev配置一个delay_bdev来达到同样的效果。Delay Bdev模块不是为了提供特定NVMe驱动延迟的高度模拟,相反,其主要目的是从大的方向上了解我们一般来说的延迟对某些特定应用程序的影响。
三、SPDK Delay Bdev模块介绍
首先,我们介绍SPDK最新代码中几个和delay_bdev相关的函数。主要包括创建delay_bdev、删除delay_bdev和更新delay_bdev的延迟的函数。然后介绍通过使用bdev_nvme_set_options函数,设置spdk host端超时时间设置和超时操作等。
1.
Bdev_delay_create函数
使用bdev_delay_create RPC创建delay_bdev。这个rpc函数有6个参数,第一个表示delay_bdev的名称,第二个代表base_bdev的名称。其余四个参数表示以下延迟值:平均读延迟、平均写延迟、P99读延迟和P99写延迟。在delay_bdev的语境中,P99延迟表示在完成I/O并提交到上层协议之前,1%的I/O至少会达到P99延迟的值。这里所有的延迟都以微秒为单位。bdev_delay_create函数的返回值为新创建bdev的名称。示例命令如下:
rpc.py bdev_delay_create -b Null0 -d delay0 -r 10 --nine-nine-read-latency 50 -w 30 --nine-nine-write-latency 90
该命令将创建一个delay_bdev,平均读和写延迟分别为10微秒和30微秒,P99读和P99写延迟分别为50微秒和90微秒。
名字 |
可选参数 |
参数类型 |
描述 |
Base_bdev_name |
Required |
String |
Bdev name |
Avg_read_latency |
Required |
Number |
平均读延迟(us) |
P99_read_latency |
Required |
Number |
P99读延迟(us) |
Avg_write_latency |
Required |
Number |
平均写延迟(us) |
P99_write_latency |
Required |
Number |
P99 写延迟(us) |
表1 bdev_delay_create函数参数及说明
2.
Bdev_delay_delete函数
使用bdev_delay_delete RPC函数来删除delay bdev。示例命令如下:
rpc.py bdev_delay_delete delay0
此函数用于删除创建的delay_bdev,函数的返回值是 true 或者 false。
名字 |
可选参数 |
参数类型 |
描述 |
name |
Required |
String |
Bdev name |
表 2 bdev_delay_delete 函数参数和说明
3.
Bdev_delay_update_latency函数
使用bdev_delay_update_latency函数,用户可以更新给定delay_bdev的延迟。可以实现延迟的重新配置。从而可以在测试上做一些有趣的尝试,例如设置较长延迟配置bdev,建立好target和host之间的I/O路径之后,不需要删除delay_bdev而是通过重新配置delay_bev来减少延迟,从而允许I/O正常流通。实例命令如下:
rpc.py bdev_delay_update_latency delay0 avg_read 1000000
此函数更新与给定delay_bdev相关I/O类型的目标延迟值。当前任何未完成的I/O将使用旧的延迟来完成I/O处理。返回值是true或者false。
名字 |
可选参数 |
参数类型 |
描述 |
Delay_bdev_name |
Required |
String |
delay bdev名称 |
Latency_type |
Required |
String |
avg_read, avg_write, p99_read, p99_write其中之一 |
Latency_us |
Required |
Number |
新的延迟值latency(us) |
表 3 bdev_delay_update_latency 函数参数和说明
这里需要特别提到的是,一般情况下,I/O都会被SPDK Bdev和驱动层及时处理。通过配置Delay Bdev,根据配置Delay的时间,超时可能会被触发,情况则会有所不同。本质上这意味着从低延迟变更为高延迟后,会因为延迟更新后新提交的I/O造成阻塞。这也是我们设计这个Delay Bdev结合host initiator端预定义的超时用例所期望得到的结果。
4.
Bdev_nvme_set_options
使用bdev_nvme_set_options为所有NVMe Bdev设置全局参数。只能在SPDK子系统初始化之前调用该RPC命令。此函数的返回值是 true 或 false。该PRC可以配置 NVMe Bdev针对IO超时的处理,比如尝试abort,reset,超时的时长等等。我们通过NVMe Bdev来结合Delay Bdev可以进行 Host 到 Target的端到端IO长延时处理。
名字 |
可选参数 |
参数类型 |
参数描述 |
Action_on_timeout |
Optional |
String |
命令超时时要采取的操作:none, reset或abort |
Timeout_us |
Optional |
Number |
每个I/O命令超时时间(us)如果为 0,则不跟踪超时 |
Timeout-admin-us |
Optional |
Number |
每个管理命令的超时时间,单位为微秒。如果为 0,则视为io超时 |
Retry_count |
Optional |
Number |
每次I/O失败前的重试次数 |
Arbitration_burst |
Optional |
Number |
以二的幂表示该值,值为111b表示没有限制 |
Low_priority_weight |
Optional |
Number |
控制器可以一次性从低优先级队列启动的最大命令数 |
Medium_priority_weight |
Optional |
Number |
控制器可以一次性从中优先级队列启动的最大命令数 |
High_priority_weight |
Optional |
Number |
控制器可以一次性从高优先级队列启动的最大命令数 |
Nvme_adminq_poll_period_us |
Optional |
Number |
管理队列轮询异步事件的频率,以微秒为单位。 |
Nvme_ioq_poll_period_us |
Optional |
Number |
I/O队列轮询完成的频率,以微秒为单位。 Default: 0(as fast as possible) |
Io_queue_reqeusts |
Optional |
Number |
为每个NVMe I/O队列分配的请求数。Default: 512 |
Delay_cmd_submit |
Optional |
Boolean |
启用延迟NVMe命令提交以允许批量处理多个命令。Default: ‘true’. |
表 4 bdev_nvme_set_options 函数参数和说明
四、使用SPDK和Kernel测试Delay Bdev的两个实例
1.
SPDK Target和Initiator的拓扑图如下所示
Physical Machine 1即SPDK Target是标准的baremetal的服务器,上面运行spdk_tgt应用程序,并且配置相应的base bdev和delay bdev。Physical Machine2即Initiator,这里主要包括两类initiator,一个是kernel initiator,另一个是spdk initiator。两台物理机使用E810网卡由100G的网线背靠背连接。同时这两台机器通过LAN Ethernet Switch连接到同一个局域网。
图1 SPDK NVMF Target和Initiator拓扑结构图
2.
SPDK NVMF Target + Delay_bdev -> SPDK Initiator实例
此测试旨在模拟非正常的网络情况下,即大量I/O在到达SPDK Initiator之前被丢弃的情况。本质上,我们试图在SPDK Initiator端触发Initiator超时,并设置对应超时产生不同的行为,比如abort,reset以及none等。
这个测试与我们所做的其他测试不同,因为它无需杀掉target或initiator端应用。相反,该测试希望target端应用能够持续运行,并适当回应SPDK initiator端应用产生的各种错误条件。我们希望在initiator上运行的bdevperf仍然可以成功完成,并且target端应用不会由于未正确处理断开连接和重新连接而崩溃。SPDK Target和SPDK Initiator的脚本示例如下:
SPDK Target端
在SPDK Target服务器端启动nvmf_tgt应用程序,并启用对 “nvmf”,“bdev”,“bdev_malloc”和“vbdev_delay”的trace跟踪
./build/bin/nvmf_tgt -i 0 -e 0xFFFF -m 0xF -L "nvmf" -L "bdev" -L"bdev_malloc" -L "vbdev_delay"
运行时使用spdk_trace命令捕获事件快照
./build/bin/spdk_trace -s nvmf -i 0
创建一个base_bdev
./scripts/rpc.py bdev_malloc_create 64 512 -b Malloc0
创建delay_bdev,我们不能在刚开始的时候将delay_bdev配置为过高延迟,否则连接操作将无法正常工作
./scripts/rpc.py bdev_delay_create -b Malloc0 -d Delay0 -r 30 -t 30 -w 30 -n 30
创建传输、子系统、添加命名空间并添加监听器
./scripts/rpc.py nvmf_create_transport -t tcp -o -u 8192
./scripts/rpc.py nvmf_create_subsystem nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
./scripts/rpc.py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Delay0
./scripts/rpc.py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t tcp -a 192.168.3.8 -s 4420
SPDK Initiator端(使用bdev_nvme_set_options rpc命令定义超时以及超时的操作等)
导出PYTHONPATH的环境路径
export PYTHONPATH=$PYTHONPATH:/path_to_spdk/scripts/
运行bdevperf 应用程序,并启用对“bdev_nvme”,“bdev”,“bdev_malloc”和“vbdev_delay”的trace跟踪
./test/bdev/bdevperf/bdevperf -z -r /var/tmp/spdk.sock -q 1 -o 4096 -w read -t 60 -f -e 0x8 --wait-for-rpc -Lbdev_nvme -L bdev -L bdev_malloc -L vbdev_delay
设置SPDK Initiator超时时间以以及超时操作等。
./scripts/rpc.py bdev_nvme_set_options --timeout-us=50000 --timeout-admin-us=0--action-on-timeout=abort -r 1
执行bdev_nvme_set_option命令后启动框架初始化
./scripts/rpc.py framework_start_init
连接控制器
./scripts/rpc.py -s/var/tmp/spdk.sock bdev_nvme_attach_controller -b NVMe0 -t tcp -a 192.168.3.8-s 4420 -f ipv4 -n nqn.2016-06.io.spdk:cnode1
执行测试
./test/bdev/bdevperf/bdevperf.py -s /var/tmp/spdk.sock perform_tests
SPDK Target端
当I/O经过SPDK Target和SPDK Initiator时,将avg_read/avg_write/p99_read和p99_write的delay_bdev延迟时间更新为1秒,睡眠3秒之后,恢复delay_bdev延迟时间为之前设定的30微秒
./scripts/rpc.py bdev_delay_update_latency Delay0 avg_read 1000000
./scripts/rpc.py bdev_delay_update_latency Delay0 avg_write 1000000
./scripts/rpc.py bdev_delay_update_latency Delay0 p99_read 1000000
./scripts/rpc.py bdev_delay_update_latency Delay0 p99_write 1000000
sleep 3
./scripts/rpc.py bdev_delay_update_latency Delay0 avg_read 30
./scripts/rpc.py bdev_delay_update_latency Delay0 avg_write 30
./scripts/rpc.py bdev_delay_update_latency Delay0 p99_read 30
./scripts/rpc.py bdev_delay_update_latency Delay0 p99_write 30
在所有脚本完成运行后,尝试获取日志消息,并在SPDK Target端log中跟踪以下关键字,以检查超时操作是否按预期工作
Key message |
|
SPDK Target |
nvme_qpair.c: 252:nvme_admin_qpair_print_command: *NOTICE*: ABORT (08) qid:0 cid:4 nsid:0 cdw10:00000001 cdw11:00000000 nvme_qpair.c: 457:spdk_nvme_print_completion: *NOTICE*: ABORTED - BY REQUEST (00/07) qid:1 cid:0 cdw0:0 sqhd:0000 p:0 m:0 dnr:0 |
这里需要描述一下,SPDK Initiator端超时的不同操作以及查找相应的关键字的方法,如下表所示
超时操作 |
日志消息描述 |
描述 |
Abort |
[2022-04-18 09:43:16.566576] nvme_qpair.c: 252:nvme_admin_qpair_print_command: *NOTICE*: ABORT (08) qid:0 cid:4 nsid:0 cdw10:00000001 cdw11:00000000 [2022-04-18 09:43:16.566610] nvme_qpair.c: 457:spdk_nvme_print_completion: *NOTICE*: ABORTED - BY REQUEST (00/07) qid:1 cid:0 cdw0:0 sqhd:0000 p:0 m:0 dnr:0 |
向nvmf target发送“Abort”命令 |
Reset |
2022-04-16 13:38:17.417393] bdev_nvme.c:2866:timeout_cb: *WARNING*: Warning: Detected a timeout. ctrlr=0x29a4460 qpair=0x29f1c70 cid=0 [2022-04-16 13:38:17.417802] bdev_nvme.c:1150:bdev_nvme_disconnected_qpair_cb: *NOTICE*: qpair 0x29f1c70 is disconnected, free the qpair and reset controller. [2022-04-16 13:38:17.417868] nvme_ctrlr.c:1650:nvme_ctrlr_disconnect: *NOTICE*: [nqn.2016-06.io.spdk:cnode1] resetting controller [2022-04-16 13:38:17.428914] bdev_nvme.c:5371:bdev_nvme_readv: *DEBUG*: read 8 blocks with offset 0xb638 [2022-04-16 13:38:17.428932] bdev_nvme.c:1543:_bdev_nvme_reset_complete: *NOTICE*: Resetting controller successful. |
向nvmf target发送“Reset”命令 |
None |
[2022-04-16 13:45:52.041266] bdev_nvme.c:2866:timeout_cb: *WARNING*: Warning: Detected a timeout. ctrlr=0x2212460 qpair=0x225fc70 cid=0 [2022-04-16 13:45:52.041684] bdev_nvme.c:2908:timeout_cb: *DEBUG*: No action for nvme controller timeout. |
向nvmf target发送“None”命令 |
表5 SPDK Initiator端timeout的不同操作及对应log信息
3.
SPDK Target + Delay_bdev ->
Kernel Initiator实例
此测试旨在模拟非正常的网络情况下,即大量I/O在到达Initiator之前被丢弃的情况。本质上,我们试图在内核态触发Initiator超时,内核态会断开与目标的连接,并尝试重新建立连接。
内核启动器的默认超时时间为30秒,在等待30秒后,内核将触发Initiator重新连接操作。当delay_bdev设置为31秒(对应启动器重新连接1次)时,测试和验证内核重新连接和超时的示例脚本。SPDK Target端和Initiator端的脚本示例如下:
SPDK Target端
在服务器端启动nvmf_tgt应用程序
./build/bin/nvmf_tgt -i 0 -e 0xFFFF -m 0xF
运行时使用spdk_trace命令捕获事件快照
./build/bin/spdk_trace -s nvmf -i 0
创建一个base_bdev
./scripts/rpc.py bdev_malloc_create64 512 -b Malloc0
创建delay_bdev,我们在刚开始的时候不能将delay_bdev配置为过高延迟,否则连接无法正常工作
./scripts/rpc.py bdev_delay_create -b Malloc0 -d Delay0 -r 30 -t 30 -w 30 -n 30
创建传输、子系统、添加命名空间并添加监听器
./scripts/rpc.py nvmf_create_transport -t tcp -o -u 8192
./scripts/rpc.py nvmf_create_subsystem nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001
./scripts/rpc.py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Delay0
./scripts/rpc.py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t tcp -a 192.168.3.8 -s 4420
内核Initiator端(Linux 内核的默认超时时间为 30 秒):
加载对应的模块
modprobe nvmet
modprobe nvmet-tcp
modprobe nvme-fabrics
连接后端设备
nvmeconnect -t tcp -n nqn.2016-06.io.spdk:cnode1 -a 192.168.3.8 -s 4420
使用fio发送 I/O,这里需要注意,超时I/O完成后,仍有10 秒的I/O
./scripts/fio-wrapper-p nvmf -i 4096 -d 1 -t write -r 60 -v
SPDK Target端
内核启动器的默认超时时间为30秒,延迟31秒以触发启动器重新连接
./scripts/rpc.py bdev_delay_update_latency Delay0 avg_read 31000000
./scripts/rpc.py bdev_delay_update_latency Delay0 avg_write 31000000
./scripts/rpc.py bdev_delay_update_latency Delay0 p99_read 31000000
./scripts/rpc.py bdev_delay_update_latency Delay0 p99_write 31000000
睡眠3秒,发送I/O
sleep 3
将这些值重置为后续I/O能够及时完成的值
./scripts/rpc.py bdev_delay_update_latency Delay0 avg_read 30
./scripts/rpc.py bdev_delay_update_latency Delay0 avg_write 30
./scripts/rpc.py bdev_delay_update_latency Delay0 p99_read 30
./scripts/rpc.py bdev_delay_update_latency Delay0 p99_write 30
在SPDK Target端,我们需要跟踪dmesg以检查重新连接是否成功。关键消息如下
# dmesg
[64770.513822] nvme nvme0: creating 40 I/O queues.
[64770.913091] nvme nvme0: mapped 40/0/0 default/read/poll queues.
[64770.928574] nvme nvme0: new ctrl: NQN "nqn.2016-06.io.spdk:cnode1", addr 192.168.3.8:4420
[64770.930075] nvme0n1: detected capacity change from 0 to 67108864
[64872.365889] nvme nvme0: queue 26: timeout request 0x34 type 4
[64872.365895] nvme nvme0: starting error recovery
[64872.372236] block nvme0n1: no usable path - requeuing I/O
[64872.384925] nvme nvme0: Reconnecting in 10 seconds...
[64882.617640] nvme nvme0: creating 40 I/O queues.
[64883.031142] nvme nvme0: Successfully reconnected (1 attempt)
五、参考文献
1. https://spdk.io/
2. https://review.gerrithub.io/c/spdk/spdk/+/462584
3. https://review.gerrithub.io/c/spdk/spdk/+/453594
4. https://review.gerrithub.io/c/spdk/spdk/+/464454
5. https://review.gerrithub.io/c/spdk/spdk/+/464459
6. https://www.youtube.com/watch?v=vRrAD1U0IRw
转载须知
DPDK与SPDK开源社区
公众号文章转载声明
推荐阅读
支持非对称命名空间访问的SPDK多路径验证
TADK v22.03 Release
手把手教你让英特尔®E810飞起来
DPDK Release 22.03
点点“赞”和“在看”,给我充点儿电吧~