Linux 中常会用到 iostat 来查看硬盘的读写状态与负载, 所以就需要我们真正的理解 iostat 的输出的含义, 本问就将讲解这些内容, 以及 iostat 的数值计算放法。
并且后文还会给出利用 node_exporter 指标模拟 iostat 输出的计算方式
iostat 常见用法, -mdx 中 m 代表以 MB 为单位输出读写带宽等信息(默认以 KB 或 扇区输出), x 代表显示额外的扩展信息, 没有 x 参数输出内容将会少很多, 后面的两个数字 5 100 代表每 5 秒输出一次, 并且一共输出 100 次, 如果第二个数子 100 不写, 则代表会一直不同的输出。
iostat -mx 5 100
iostat 输出如下, 注意到第一次输出是从开机以来到现在的平均数值(为什么会有这样一段输出后文将会解释)
[root@ linux ~]# iostat -mx 5 100
Linux 3.10.0-327.el7.x86_64 (test70) 02/21/2020 _x86_64_ (40 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
1.91 0.00 2.06 0.00 0.00 96.04
Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 35.02 37.74 19.22 9.96 0.22 0.24 32.54 0.05 1.74 0.27 4.58 0.63 1.85
sdc 0.00 0.04 0.00 0.37 0.00 0.19 1070.55 0.00 7.38 0.13 7.44 0.13 0.00
sde 0.00 0.02 0.00 0.24 0.00 0.19 1637.62 0.00 0.93 0.14 0.94 0.09 0.00
sdb 0.00 0.02 0.02 0.28 0.00 0.00 33.95 0.00 0.17 0.67 0.14 0.12 0.00
avg-cpu: %user %nice %system %iowait %steal %idle
1.91 0.00 2.06 0.00 0.00 96.04
Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 1.60 0.00 3.20 0.00 0.03 19.56 0.04 12.31 0.00 12.31 12.31 3.94
sdc 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sde 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
参数 | 说明 |
---|---|
rrqm / s | 每秒合并读操作的次数, 这个指卡块设备驱动进行的合并, 例如前后两次要写入的数据正好位于相邻的位置, 那么就会将这两个请求被内核的 I/O scheduler合并成一个请求让块设备写入, 一般来说随机块设备时由于写入的数据分散在块设备的不同地址上,所以合并数会较小,顺序访问由于要访问的数据都为与相邻的地址, 所以合并数会比较大. |
wrqm/s | 每秒合并写操作的次数, 同上 |
r/s | 每秒读操作的次数, 合并之后的读操作次数, 如果发生了合并,此值会小于从上层操作系统中的应用看到的操作次数 |
w/s | 每秒写操作的次数, 同上 |
rMB/s | 每秒读取的MB字节数 |
wMB/s | 每秒写入的MB字节数 |
avgrq-sz | 每个IO的平均扇区(512字节)数,这个数值代表了这段时间内的平均写入IO的大小,所以如果使用测试工具如 fio 指定 bs=4KiB 那么这个数值就会时 8 ( 4 KiB / 512 byte = 8) |
avgqu-sz | 平均未完成的IO请求数量,即平均意义山的请求队列长度, 是指IO在写入或读取前排队的事件 |
await | 平均每个IO所需要的时间,包括在队列等待的时间,也包括块设备处理本次请求的有效时间。一般如果访问比较随机此值会偏大(寻道时间长), 而对于网络块设备如RBD, 需要考虑的就是整个IO路径以及网络传输的耗时 |
r_wait | 每个读操作平均所需要的时间,不仅包括硬盘设备读操作的时间,也包括在内核队列中的时间。 |
w_wait | 每个写操平均所需要的时间,不仅包括硬盘设备写操作的时间,也包括在队列中等待的时间。 |
%util | 工作时间或者繁忙时间占总时间的百分比, 表示该设备有I/O(即非空闲)的时间比率,不考虑I/O有多少,只考虑有没有I/O. 所以单纯的看这个值并不能说明块设备访问是否饱和了, 例如如果一个块设备可以并发处理5个请求,请求处理是时间0.1s, 1s内如果同时来了5个请求,此时块设备0.1s 就可以处理完这5个请求, 如果其余时间没有请求, 那么此时%util = 10, 如果1s内每0.1s来一个请求, 那么此时 %util = 100, 但实际块设还远未饱和, 还能处理很多请求, 究其原因是 %util 并未考虑设备的并发, 只是反应块设备有IO的时间占比 |
svctm | 已经废弃, 没有实际参考价值,由于/proc/diskstats 的记录里没有对于单个IO的记录, 并且硬盘基本都是支持并发的, 所以基于 /proc/diskiostats 的 iostat工具没有任何一输出项表示的是硬盘设备平均每次IO的时间。 |
/proc/diskstats
要想进一步理解 iostats 的参数的含义, 还需要理解 iostats 这些参数是怎么来的, iostat 的数据是通 /proc/diskstats 中的数据计算而来的, 这个文件由内核写入, 反应块设备的I/O情况
[root@root ~]$ cat /proc/diskstats
8 0 sda 362793215 661203921 8484710773 97338189 187964922 712494716 9437440883 861376111 0 349285322 958724349
8 1 sda1 12481 0 33636 2691 1154 9 5105 1266 0 3937 3951
8 2 sda2 360240351 661203706 8171560440 96092483 161601623 703168025 6939827368 545048656 0 150195914 641192119
8 3 sda3 12128 0 27957 1683 3055 483 175543 18858 0 13762 20531
8 4 sda4 9 0 136 0 0 0 0 0 0 0 0
8 5 sda5 2525867 215 313066020 1240880 25968849 9326199 2497432867 313657458 0 202004420 314868393
8 32 sdc 59894 0 2103061 7722 6985995 669449 7538335920 51975675 0 922943 51980874
8 64 sde 64733 0 1693261 8941 4533144 343399 7525909984 4277973 0 399364 4285223
8 16 sdb 328023 107 152397875 221253 5216912 341949 35838675 721388 0 690826 939408
8 17 sdb1 163 0 5394 19 0 0 0 0 0 16 19
8 18 sdb2 181 0 9064 41 0 0 0 0 0 32 41
......
从第 4 列(硬盘名后面的一列)开始的各列依次的含义
参数 | 说明 |
---|---|
rd_ios | 读操作的总次数。 |
rd_merges | 总合并读操作的次数。如果两个读操作读取相邻的数据块时,可以被合并成一个,以提高效率。合并的操作通常是I/O scheduler(也叫elevator)负责的。 |
rd_sectors | 总读取的扇区数量。 |
rd_ticks | 总读操作消耗的时间(以毫秒为单位)。包括内核队列中等待的时间和从块设备读取数据的时间。此值把每一个I/O所消耗的时间累加在一起的时间, 发生并发也会把并发的每个IO都计入累积, 所以理论上这个时间可能比系统运行时间还要大 |
wr_ios | 总写操作的次数。 |
wr_merges | 总合并写操作的次数。 |
wr_sectors | 总写入的扇区数量。 |
wr_ticks | 总写操作消耗的时间(以毫秒为单位)。包括内核队列中等待的时间和向块设备写入数据的时间。此值把每一个I/O所消耗的时间累加在一起的时间, 发生并发也会把并发的每个IO都计入累积, 所以理论上这个时间可能比系统运行时间还要大 |
in_flight | 当前未完成的I/O数量。在I/O请求进入队列时该值加1,在I/O结束时(完成IO操作)该值减1, 反映当前的内核队列中有多少IO。注意:开始是I/O请求进入队列时,而不是提交给硬盘设备时。所以此值是包括正在排队的IO和正在和块设备进行IO写入/读取但是还未完成的IO |
io_ticks | 该设备用于处理I/O的自然总时间(wall-clock time)。字段#9(in_flight)不为零的时候io_ticks保持计时,字段#9(in_flight)为零的时候io_ticks停止计时(只管有没有IO, 不管有多少IO)请注意io_ticks与rd_ticks(字段#4)和wr_ticks(字段#8)的区别,rd_ticks和wr_ticks是把每一个I/O所消耗的时间累加在一起,因为硬盘设备通常可以并行处理多个I/O,所以rd_ticks和wr_ticks往往会比此值大(并发越高越明显)。 |
time_in_queue | 对字段#10(io_ticks)的加权值。io_ticks * in_flight 字段#10(io_ticks)是自然时间,不考虑当前有几个I/O,而time_in_queue是用当前的I/O数量(即字段#9 in-flight)乘以自然时间。虽然该字段的名称是time_in_queue,但并不真的只是在队列中的时间,其中还包含了硬盘处理I/O的时间。iostat在计算avgqu-sz时会用到这个字段。 |
在使用 iostat 时,iostat 第一次输出的是开机以来到运行 iostats 的平均值,从第二次输出才是当前的数据,究其原因即使因为 /proc/diskstats 中提供的是累计值, iostat 需要获取差值才能计算出当前的性能数据。 所以第一次输出就相当于当前的值到系统开机时的差值除以开机时间计算出来的平均值(/proc/ 是挂载在内存中的,所以重启后所有数据就会清零)
参数 | 说明 |
---|---|
tps | 每秒I/O次数=[(Δrd_ios+Δwr_ios)/Δt] |
r/s | 每秒读操作的次数=[Δrd_ios/Δt] |
w/s | 每秒写操作的次数=[Δwr_ios/Δt] |
rkB/s | 每秒读取的千字节数=[Δrd_sectors/Δt]*[512/1024] |
wkB/s | 每秒写入的千字节数=[Δwr_sectors/Δt]*[512/1024] |
rrqm/s | 每秒合并读操作的次数=[Δrd_merges/Δt] |
wrqm/s | 每秒合并写操作的次数=[Δwr_merges/Δt] |
avgrq-sz | 每个I/O的平均扇区数=[Δrd_sectors+Δwr_sectors]/[Δrd_ios+Δwr_ios] |
avgqu-sz | 平均未完成的I/O请求数量=[Δtime_in_queue/Δt](手册上说是队列里的平均I/O请求数量,更恰当的理解应该是平均未完成的I/O请求数量。) |
await | 每个I/O平均所需的时间=[Δrd_ticks+Δwr_ticks]/[Δrd_ios+Δwr_ios](不仅包括硬盘设备处理I/O的时间,还包括了在kernel队列中等待的时间。) |
r_await | 每个读操作平均所需的时间=[Δrd_ticks/Δrd_ios]不仅包括硬盘设备读操作的时间,还包括了在kernel队列中等待的时间。 |
w_await | 每个写操作平均所需的时间=[Δwr_ticks/Δwr_ios]不仅包括硬盘设备写操作的时间,还包括了在kernel队列中等待的时间。 |
%util | 该硬盘设备的繁忙比率=[Δio_ticks/Δt]表示该设备有I/O(即非空闲)的时间比率,不考虑I/O有多少,只考虑有没有 |
有关于 node_exporter 如果你使用 node_exporter 监控服务器, 那么也可以通过 node_exporter 的指标来模拟 iostat 的输出, 做到远程监控磁盘, 因为 node_exporter 的 disk 相关指标就是从 /proc/diskstats 里面获取的, 通过 node_exporter 的数据可以计算得到采样周期内硬盘数据的平均值, 所以prometheus 采集 node_exporter 的时间间隔不宜过长。
node_exprter 指标 | 对应 /proc/diskstats | 对应 /proc/diskstats 列数 |
---|---|---|
written_bytes_total | wr_sectors | 10 |
writes_merged_total | wr_merges | 9 |
writes_completed_total | wr_ios | 8 |
write_time_seconds_total | wr_ticks | 11 |
reads_merged_total | rd_merges | 5 |
reads_completed_total | rd_ios | 4 |
read_time_seconds_total | rd_ticks | 7 |
read_bytes_total | rd_sectors | 6 |
io_time_weighted_seconds_total | time_in_queue | 14 |
io_time_seconds_total | io_ticks | 13 |
io_now | in_flight | 12 |
iostat 输出模拟的计算公式
iostat 输出 | node_exporter 指标计算公式 |
---|---|
tps | (Δreads_completed_total + Δwrites_completed_total ) / Δt |
r/s | Δreads_completed_total / Δt |
w/s | Δwrites_completed_total / Δt |
rKiB/s | (Δread_bytes_total / Δt) / 1024 |
rMiB/s | (Δread_bytes_total / Δt) / (1024 * 1024) |
wKiB/s | (Δwritten_bytes_total / Δt) / 1024 |
wMiB/s | (Δwritten_bytes_total / Δt) / (1024 * 1024) |
rrqm/s | Δreads_merged_total / Δt |
wrqm/s | Δwrites_merged_total / Δt |
avgrq-sz | (Δread_bytes_total / 512 + Δwritten_bytes_total / 512) / (Δreads_completed_total + Δwrites_completed_total) |
avgqu-sz | Δio_time_weighted_seconds_total / Δt |
await | (Δread_time_seconds_total * 1000 + Δwrite_time_seconds_total * 1000) / (Δreads_completed_total + Δwrites_completed_total) |
r_await | Δread_time_seconds_total * 1000 / Δreads_completed_total |
w_await | Δwrite_time_seconds_total * 1000 / Δwrites_completed_total |
%util | Δio_time_seconds_total / Δt * 100% |
ps:
Δio_time_seconds_total / Δt
Δread_time_seconds_total 代表两次采样的 read_time_seconds_total 差值, Δt 代表表两次采样的时间差