没有口径的「更快」会毁掉选型;有字段却不懂语义的 dashboard 会毁掉排障。第 12 篇 给出了 RPC 配置面;本篇只回答:进程内统计在量什么、官方 Performance Report 能证明什么、不能证明什么。
本文是系列第 13 篇。不粘贴本机未跑的 fio/RPC 数字,不把官方 PDF 里的 IOPS 抄成本站结论。
本文是「SPDK / 用户态存储栈」系列第 13 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 12 篇 · JSON-RPC 配置面与生命周期 第 13 篇 · 可观测与性能口径 iostat 语义与 Report 读法 第 14 篇 · 排障坐标系 五轴故障清单
版本锚定:SPDK v26.01;
bdev_get_iostat支持names数组查询多 bdev(changelog);结构字段见spdk_bdev_io_stat。Performance Reports 入口:https://spdk.io/doc/performance_reports.html(26.01 页列出当版 NVMe-oF RDMA 等报告,硬件写在标题上)。
一、先分清三种「性能说法」
| 说法 | 合法用途 | 非法用途 |
|---|---|---|
进程内 RPC 统计(如
bdev_get_iostat) |
同一进程、同一配置下看趋势、找异常层 | 跨机器、跨版本当排行榜 |
| 官方 Performance Reports | 学习方法、硬件代际、测试脚本与参数组合 | 当成「你的盘也能跑到这个数」 |
| 本机 fio / 应用 SLO | 唯一能支撑上线结论的数字 | 在无直通 NVMe/RDMA 环境伪造 |
本站实验台账约定:WSL2 / 无直通环境 禁止 伪造输出。下文只讲语义与读法。
二、bdev_get_iostat:字段在量什么
RPC(v26.01 JSON-RPC):
- 无参数:列出全部 bdev 统计;
names:数组,指定多个设备;per_channel:是否展开每 channel;reset_mode:取数后是否重置——all/maxmin/error/none(默认none)。
对应累计结构
spdk_bdev_io_stat(文档字段,删减说明:以头文件为准)核心包括:
| 字段族 | 含义 |
|---|---|
bytes_read /
num_read_ops(写、unmap、copy 同理) |
字节与操作计数 |
*_latency_ticks、min_*、max_* |
延迟用 tick 累计/极值,不是现成微秒 |
ticks_rate(响应里常见
tick_rate) |
把 tick 换成时间的尺子 |
io_error 相关 |
错误计数;可与 reset 模式联动 |
换算关系(概念,非本机测值):
\[ t_{\text{sec}} = \frac{\text{latency\_ticks}}{\text{tick\_rate}},\quad \overline{t}_{\text{read}} = \frac{\text{read\_latency\_ticks}}{\text{num\_read\_ops} \cdot \text{tick\_rate}} \quad (\text{num\_read\_ops}>0) \]
读数纪律:
- 平均延迟由累计 ticks 与 ops
推出;
min/max是极值,不是百分位。 - 队列深度相关字段(如
queue_depth、io_time、weighted_io_time)依赖轮询采样周期配置;未启用或周期不同,不能跨实例比绝对值。 bdev_reset_iostat/ 带 reset 的 get:文档写明——一个消费者重置,影响所有其他消费者。监控与人工排查共用计数器时,禁止「随手 reset」当清屏。
NVMe 多路径场景另有
bdev_nvme_get_path_iostat(路径级);与 bdev
聚合统计分层看,避免把「单路径挂了」读成「整盘没 I/O」。
命令形态(输出略;需本机进程):
scripts/rpc.py bdev_get_iostat
scripts/rpc.py bdev_get_iostat --help # 查看 names / per_channel / reset 参数三、延迟「直方图」:概念与边界
bdev 层可启用直方图类观测(版本演进中曾增加粒度与 min/max 配置以控制内存)。读直方图时只承诺三件事:
- 桶是离散近似,不是示波器连续曲线;跨版本桶宽可能变。
- 直方图回答分布形状(是否双峰、长尾是否变厚);不能在未校准 clock 与队列深度时跨机器比桶计数。
- 与
min/maxticks 的关系:极值可被偶发一次 I/O 拉爆;直方图更适合看「主体落在哪一段」,但仍需声明采样窗口与是否 reset 过。
tracepoint
框架(--tpoint-group)属于低开销实验性路径(App
Overview 注明文档仍在完善)。排障优先用 iostat /
传输层计数;把 trace 当日常全量采集前先量 CPU
与落盘成本——这是开放运维问题,不是「打开即真相」。
flowchart TD
io["bdev I/O complete"] --> cnt["ops / bytes counters"]
io --> ticks["latency ticks min/max/sum"]
io --> hist["optional histogram buckets"]
ticks --> rate["divide by tick_rate"]
hist --> shape["distribution shape only"]
四、怎么读官方 Performance Reports
入口页按发布列出报告;26.01 例如标题即绑定硬件(如 NVMe-oF RDMA on NVIDIA BlueField3)。读法:
| 步骤 | 看什么 | 得出什么 |
|---|---|---|
| 1 | 报告对应的 SPDK 版本与硬件型号 | 能否外推到你的代际 |
| 2 | 拓扑(本机 bdev / vhost / NVMe-oF TCP | RDMA) |
| 3 | 队列深度、块大小、读写比、核 mask | 口径是否可比 |
| 4 | 用的 SPDK 应用与是否 polling/interrupt | 与 第 9 篇 模式是否一致 |
| 5 | 图表纵轴单位与是否含 CPU 占用 | 避免只比 IOPS 忽略核税 |
合法引用句式:「官方 SPDK x.y … Performance Report(硬件 H)在条件 C 下报告了指标 M」——这是外部、方法与硬件绑定的引用。
非法句式:「因此本机 / 本系列测得同等 IOPS」或「所以 SPDK 一定比内核快 N 倍」。
24.05 等更早报告对 NVMe bdev、vhost、TCP/RDMA 更全;跨大版本比较时,先核对 changelog 是否改了默认传输或统计口径,再谈曲线。
五、观测接入排障与发布门禁
统计回答「哪一层在忙 / 错 / 空」;不回答「该不该上 SPDK」。把观测接进门禁时,建议最少三类信号,且都要写清标签(进程、sock 路径、bdev 名、NQN):
- 存活与配置对账:关键 bdev / listener / vhost controller 是否存在(RPC 查询,不是「进程在」)。
- 速率与错误:ops、bytes、error 的滑动窗口;禁止用单次绝对值当 SLO,除非声明窗口与是否 reset。
- 延迟形状:由 ticks 推出的均值
+(若启用)直方图桶占比;
max只作「是否出现极端样本」的告警辅信号。
发布前后对比必须满足:同一 tick_rate 理解、同一 reset 纪律、同一 QD/块大小。否则「发布后变慢」可能只是有人清过计数,或采样周期变了。
与官方 report 的关系:可以把报告里的测试脚本与参数表当成实验设计参考,把报告里的曲线绝对值当成别的机器上的故事。门禁通过条件应写成「相对基线变更不超过团队约定」或「误差计数为零」,而不是「达到某 PDF 的 IOPS」。
六、直方图、百分位与「不能说的话」
运维语言里常说 p99;spdk_bdev_io_stat 的
min/max/sum
不直接等于百分位。要从直方图估百分位,必须:
- 知道桶边界与是否在窗口内 reset;
- 接受桶宽带来的量化误差;
- 不在跨版本、跨
tick_rate机器间直接比桶计数。
若系统未启用直方图,只用 max ticks 谈「尾延迟」,会把偶发一次管理 I/O 或热路径外的提交误判成数据面回归。更稳的做法是:应用侧或 initiator 侧用明确窗口的时延直方图,SPDK 侧用 ops/error 与均值 ticks 做分层,两边时间对齐后再归因。
trace 与日志的成本模型也要写进容量规划:全开
-L 或宽 trace group
可能改变你要测的延迟本身。观测税与数据面税必须分开记账,这与代理数据面「全量
access
log」问题同构(对照站内可观测性系列的治理直觉即可,不在此展开平台细节)。
七、谱系与争论
谱系(简):块层 iostat 传统提供 ops/字节/繁忙度 → 用户态栈把同类计数挂到 bdev 完成路径,并用 tick 对齐 TSC/时钟源 → 官方 Performance Report 用受控硬件讲「方法可达的上界故事」。三者层级不同,不可互相替代。
争论:轮询栈是否「必须」看 CPU%?一方认为高占用是健康;一方要求按有效 IOPS/核做效率账。没有 workload 与核 mask 时,争论无解。另一争论是监控是否允许 reset 计数:运维方便 vs 多租户共享计数器——文档已站在「影响所有消费者」一侧,产品若要多租户隔离需自建采样副本,而不是争用同一 reset。
八、本篇收束
能解释字段与外部报告边界,才有资格谈性能;谈性能而不写硬件与口径,等于没谈。
下一篇把症状映射到五轴,把本篇信号当成「选轴之后看什么」。
把观测做成「可机器核对」时,优先存原始字段:ops、bytes、latency_ticks、tick_rate、error,再在查询层换算成秒与带宽。先存已换算的浮点微秒,会在 tick_rate 理解错误或跨主机汇总时埋雷。看板与告警规则应引用同一换算库,避免有人用固定 1e9、有人用响应里的 tick_rate 各算各的。
对官方 Performance Reports 的二次使用,建议在团队内建一个「引用模板」:版本、硬件型号、拓扑、块大小、队列深度、读写比、是否 polling、指标名称与单位。缺任一项的引用,不允许进入架构评审材料。这样可以把「看过 PDF」提升为「可复核的外部证据」,同时挡住把特定 DPU/RNIC 报告抄到消费级 NVMe 笔记本上的做法。
采样频率也有机制后果:对 bdev_get_iostat
过高频率轮询会增加 RPC
与消息传递开销;过低则看不见短窗口拥塞。实践上应把「监控采样」与「事故抓取」分开:前者低频、只读、禁止
reset;后者在明确窗口内抓 per_channel
或直方图,并在工单中记录是否 reset。若两套系统共用同一 reset
入口,再精致的看板也会互相踩踏。
延迟数字还要与队列深度、未完成 I/O 数一起读。只报平均延迟、不报当时 QD,等于隐瞒负载。官方 report 通常会交代 QD 与核 mask;你的内部基线若缺这两项,就无法判断回归来自软件变更还是测试注入变了。把 QD、块大小、读写比、核 mask 称为「口径四元组」,任何对比缺少其中一项即判为不可比。
还要注意「层间重复计数」:nvmf 入口、bdev、下层 nvme 可能各自暴露统计,直接相加会双计。归因时应选定一层主真相(通常是最贴近症状的那一层),其他层只做交叉验证。vhost 场景下,guest 内 iostat 与主机 bdev iostat 的差额,往往落在虚拟化通知与轮询批次,而不是简单的「少了一半性能」叙事;没有同步时钟与同窗口负载,不要把差额写成结论。
若团队使用外部时序数据库,导出 RPC JSON 时保留
name 与实例标签,避免所有盘都叫
Nvme0n1
导致序列撞名。多主机汇总前先做标签规范,否则官方 report
式的「单机方法」会被错误放大成集群平均值。
口径四元组(QD、块大小、读写比、核 mask)应与软件版本、bdev 名一并写入每一次内部基线的元数据。缺少元数据的曲线,三个月后无法解释,只能重测。重测成本通常高于当初多写一行标签。
九、参考资料
规范 / 官方文档(A)
- JSON-RPC:
bdev_get_iostat、bdev_reset_iostat、bdev_nvme_get_path_iostat。 spdk_bdev_io_stat结构文档。- Performance Reports 索引页;各 PDF 内硬件与方法章节。
- Changelog(v26.01):
names字段等 iostat 相关变更。
源码(A)
include/spdk/bdev.h(及 iostat 相关)、lib/bdev/@ v26.01。
→ 上一篇:JSON-RPC 与生命周期 · 系列目录 · 下一篇:排障坐标系
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【SPDK 用户态存储】用户态存储全景:从 Reactor 到 NVMe-oF 的坐标系
定位 SPDK 相对内核块层、O_DIRECT+io_uring 与 NVMe 协议百科的生态位;钉住 I/O 路径、线程模型、设备独占、块抽象、远端传输五条坐标系,并给出 16 篇阅读路线。
【SPDK 用户态存储】Reactor 与线程模型:Event、Poller 与 Message Passing
钉住 SPDK event framework:每核 reactor、事件队列、poller 轮询与跨核消息;说明 shared-nothing 边界,以及在 reactor 上阻塞等于饿死同核所有工作。
【SPDK 用户态存储】环境与绑盘:Hugepage、CPU 亲和与 VFIO/UIO
说明 SPDK 运行前 env:大页分配、CPU 绑核语义、setup.sh 将 NVMe 从内核驱动解绑到 VFIO/UIO;钉住设备独占代价与 reset 回退路径。
【SPDK 用户态存储】用户态 NVMe 驱动:Controller、Qpair 与 Poll Group
拆解 SPDK 被动式 NVMe 库:probe/attach、admin 与 I/O qpair、完成轮询与 poll group;说明单线程独占 qpair、零拷贝提交路径及相对内核驱动的边界。