I/O 统计字段¶
内核通过 /proc/diskstats 和 /sys/block/<device>/stat 暴露磁盘统计信息。这些统计信息通常通过诸如 sar 和 iostat 等工具进行访问。
以下是使用带有两个分区的磁盘的示例
/proc/diskstats:
259 0 nvme0n1 255999 814 12369153 47919 996852 81 36123024 425995 0 301795 580470 0 0 0 0 60602 106555
259 1 nvme0n1p1 492 813 17572 96 848 81 108288 210 0 76 307 0 0 0 0 0 0
259 2 nvme0n1p2 255401 1 12343477 47799 996004 0 36014736 425784 0 344336 473584 0 0 0 0 0 0
/sys/block/nvme0n1/stat:
255999 814 12369153 47919 996858 81 36123056 426009 0 301809 580491 0 0 0 0 60605 106562
/sys/block/nvme0n1/nvme0n1p1/stat:
492 813 17572 96 848 81 108288 210 0 76 307 0 0 0 0 0 0
这两个文件包含相同的 17 个统计信息。/sys/block/<device>/stat 包含 <device> 的字段。在 /proc/diskstats 中,这些字段前缀为主次设备号和设备名。在上面的例子中,两个文件中 nvme0n1 的第一个统计值都是 255999。
对于监视少量已知磁盘集合,sysfs 的 stat 文件非常高效。如果你要跟踪大量的设备,/proc/diskstats 通常是更好的选择,因为它避免了为每个快照打开和关闭多个文件的开销。
除了第 9 个字段随着 I/O 完成会重置为零外,所有字段都是累积的单调计数器。其余字段在启动、设备重新连接或重新初始化,或者底层计数器溢出时重置。读取这些计数器的应用程序在比较统计快照时,应检测并处理重置情况。
每组统计信息仅适用于指定的设备;如果你想要系统范围的统计信息,则必须找到所有设备并将其全部相加。
- 字段 1 -- 已完成的读取次数 (unsigned long)
这是成功完成的读取总次数。
- 字段 2 -- 合并的读取次数,字段 6 -- 合并的写入次数 (unsigned long)
为了提高效率,相邻的读写操作可能会被合并。因此,两个 4K 读取在最终提交给磁盘之前可能会变成一个 8K 读取,因此它将被计算(并排队)为一个 I/O。该字段让你了解这种情况发生的频率。
- 字段 3 -- 读取的扇区数 (unsigned long)
这是成功读取的扇区总数。
- 字段 4 -- 花费在读取上的毫秒数 (unsigned int)
这是所有读取操作花费的总毫秒数(从
blk_mq_alloc_request()到__blk_mq_end_request()进行测量)。- 字段 5 -- 已完成的写入次数 (unsigned long)
这是成功完成的写入总次数。
- 字段 6 -- 合并的写入次数 (unsigned long)
请参见字段 2 的描述。
- 字段 7 -- 写入的扇区数 (unsigned long)
这是成功写入的扇区总数。
- 字段 8 -- 花费在写入上的毫秒数 (unsigned int)
这是所有写入操作花费的总毫秒数(从
blk_mq_alloc_request()到__blk_mq_end_request()进行测量)。- 字段 9 -- 当前正在进行的 I/O 数量 (unsigned int)
唯一应该归零的字段。当请求被分派给相应的
struct request_queue时递增,当它们完成时递减。- 字段 10 -- 花费在执行 I/O 上的毫秒数 (unsigned int)
只要字段 9 不为零,该字段就会一直增加。
自 5.0 版本起,该字段在至少有一个请求开始或完成时计算 jiffies。如果请求运行超过 2 个 jiffies,在并发请求的情况下,某些 I/O 时间可能不会被计入。
- 字段 11 -- 花费在执行 I/O 上的加权毫秒数 (unsigned int)
在每次 I/O 开始、I/O 完成、I/O 合并或读取这些统计信息时,该字段会增加:当前进行的 I/O 数量(字段 9)乘以自该字段上次更新以来花费在 I/O 上的毫秒数。这可以为 I/O 完成时间和可能积压的工作量提供一个简单的衡量标准。
- 字段 12 -- 已完成的 discard 次数 (unsigned long)
这是成功完成的 discard 总次数。
- 字段 13 -- 合并的 discard 次数 (unsigned long)
请参见字段 2 的描述
- 字段 14 -- discard 的扇区数 (unsigned long)
这是成功 discard 的扇区总数。
- 字段 15 -- 花费在 discard 上的毫秒数 (unsigned int)
这是所有 discard 操作花费的总毫秒数(从
blk_mq_alloc_request()到__blk_mq_end_request()进行测量)。- 字段 16 -- 已完成的 flush 请求数
这是成功完成的 flush 请求总数。
块层会合并 flush 请求,并且一次最多执行一个。这统计的是由磁盘执行的 flush 请求。分区不进行此项跟踪。
- 字段 17 -- 花费在 flush 上的毫秒数
这是所有 flush 请求花费的总毫秒数。
为了避免引入性能瓶颈,在修改这些计数器时不会持任何锁。这意味着当并发更改发生冲突时,可能会引入微小的误差,因此(例如)将每个分区发出的所有读取 I/O 相加应该等于对磁盘发出的读取 I/O……但由于缺乏锁机制,结果可能只是非常接近。
在 2.6 及更高版本中,每个 CPU 都有计数器,这使得缺乏锁机制几乎不成问题。读取统计信息时,会把各个 CPU 的计数器相加(可能会超出它们相加的目标 unsigned long 变量的范围),并将结果返回给用户。没有提供用于直接访问各个 CPU 计数器的便捷用户接口。
自 4.19 版本起,请求时间以纳秒精度进行测量,并在在此接口中显示之前截断为毫秒。
磁盘与分区¶
2.4 与 2.6 及更高版本的 I/O 子系统之间有重大更改。因此,某些统计信息消失了。将相对于分区的磁盘地址转换为相对于宿主磁盘的磁盘地址的操作发生得更早。现在,所有的合并和计时都发生在磁盘级别,而不是像 2.4 中那样同时发生在磁盘和分区级别。因此,在 2.6 及更高版本中,你会看到分区与磁盘的统计信息输出有所不同。在 2.6 及更高版本的机器上,分区只有 四个 可用字段。这在上面的例子中有所体现。
- 字段 1 -- 发出的读取次数
这是发往该分区的读取总次数。
- 字段 2 -- 读取的扇区数
这是请求从该分区读取的扇区总数。
- 字段 3 -- 发出的写入次数
这是发往该分区的写入总次数。
- 字段 4 -- 写入的扇区数
这是请求写入该分区的扇区总数。
请注意,由于地址已被转换为磁盘相对地址,并且未保留分区相对地址的记录,因此读取的后续成功或失败不能归因于该分区。换句话说,分区的读取次数是在分区排队之前不久计数的,而整块磁盘则是在完成时计数的。这是一个微妙的区别,对于大多数情况来说可能并不重要。
更重要的是,在合并之前(针对分区)和合并之后(针对磁盘)统计读/写次数所带来的误差。由于典型的工作负载通常包含大量连续且相邻的请求,因此发出的读/写次数可能会比完成的读/写次数高出数倍。
在 2.6.25 中,分区的完整统计集再次可用,并且磁盘和分区的统计信息再次保持一致。由于我们仍然不保留分区相对地址的记录,因此操作会归属于在最终合并后包含请求第一个扇区的分区。由于请求可以跨分区合并,这可能会导致一些(可能微不足道的)误差。
其他说明¶
在 2.6 及更高版本中,sysfs 默认未挂载。如果你的 Linux 发行版尚未添加它,这是你需要添加到 /etc/fstab 中的行
none /sys sysfs defaults 0 0
在 2.6 及更高版本中,所有磁盘统计信息都从 /proc/stat 中移除了。在 2.4 中,它们同时出现在 /proc/partitions 和 /proc/stat 中,尽管 /proc/stat 中的格式与 /proc/partitions 中的格式有很大不同(如果你的系统有的话,请参阅 proc(5))。