原文地址:https://brian-candler.medium.com/interpreting-prometheus-metrics-for-linux-disk-i-o-utilization-4db53dfedcfc
原文标题:Interpreting Prometheus metrics for Linux disk I/O utilization
原文作者:Brian Candler
原文写于:2021/01/29
译者:驱蚊器喵#ΦωΦ
翻译水平有限,有不通顺的语句,请见谅。
Prometheus 是一个指标收集系统,它的 node_exporter 暴露了丰富的系统指标。
在本文中,我将对磁盘 I/O 的各个指标进行讲解。这些指标提供了很多磁盘的关键信息,包括关于你的磁盘性能、磁盘忙碌程度以及你的应用程序正在经历的 I/O 延迟。
Grafana 有很多 node_exporter
的面板,但并不是所有的面板都能正确标注统计信息。因此,本文的内容是很值得你了解的。
原始磁盘统计信息
在 Linux 系统中,node_exporter
从 /proc/diskstats
读取磁盘指标。这个文件的内容格式在内核文档中写有;前三列标注设备,随后的字段是详细数据。
每一个 prometheus 指标都直接对应于该文件中的一个字段,在 NewDiskstatsCollector
的源代码中可以找到。所以理解 Prometheus 指标的第一步是将指标与它们对应的内核源数据相匹配。
为了方便理解,我将内核文档放在这里,并在每个字段中添加了对应的 prometheus 指标名称。
1 | node_disk_reads_completed_total (field 1) |
node_exporter
暴露了由内核返回的原始数据,除了几个进行了缩放数据。(如果内核返回的是扇区数,node_exporter
将其乘以扇区大小,成为字节数;如果内核报告的时间是毫秒,node_exporter
将其乘以 0.001,成为秒数。我已经调整了上面的描述来匹配)
注意:旧的内核不会返回所有显示的字段。有些系统可能只返回字段 1-11 的数据;有些则只返回字段 1-15 。
分析数据: iostat
我们已经得到了以计数器和累积时间形式的原始数据。我们如何从这些数据解释,在数据采样的每个时间点之间发生了什么?
为了回答这个问题,我首先要看看一个核心的 Linux 工具,iostat
(在 Ubuntu 中,iostat
是 sysstat
包的一部分)。它解释了相同的底层磁盘统计 diskstats
,看看它是如何做到的,这会很有启发。
通常运行 iostat
的方法是:iostat 5 -x
.(-x Display extended statistics. 展示扩展信息)
这意味着:每 5 秒钟对统计资料进行采样,并以扩展形式显示结果。下面是一些输出的例子。
1 | avg-cpu: %user %nice %system %iowait %steal %idle |
你将看到的第一组统计数据是系统启动后整个时间的平均值(可能是几个月或几年)。在这之后,你会得到涵盖每个 5 秒钟周期的数据。
逐一查看这些列,以下是 iostat(1)
手册中对它们的描述。
r/s
: 设备每秒完成的读取请求数(合并后).w/s
: 设备每秒完成的写入请求数(合并后).rkB/s
: 每秒从设备上读取的 KB[^1] 数wkB/s
: 每秒写到设备上的 KB[^1] 数。rrqm/s
: (字母含义,read requests queue merge) 每秒合并到设备上的读取请求数wrqm/s
: (字母含义,write requests queue merge) 每秒合并到设备上的写入请求数.%rrqm
: 读取请求在发送到设备前被合并的百分比.%wrqm
: 写入请求在发送到设备前被合并的百分比.r_await
: 向设备发出的读请求被送达的平均时间(以毫秒计)。这包括请求在队列中花费的时间和处理的时间.w_await
: 向设备发出的写请求被送达的平均时间(以毫秒计)。这包括请求在队列中花费的时间和处理的时间。aqu-sz/avgqu-sz
: (字母含义,average queue - size) 发给设备的请求平均队列长度.rareq-sz
: (字母含义,read average request - size) 向设备发出的读取请求的平均大小(以 KB 为单位).wareq-sz
: (字母含义,write average request - size) 向设备发出的写请求的平均大小(以 KB 为单位).svctm
: (字母含义,service time) 向设备发出的I/O请求的平均服务时间. 因其不可靠,已从当前版本的iostat
中删除.%util
: 向设备发出I/O请求的时间的百分比(设备的带宽利用率)。当这个值接近 100% 时,设备就会出现饱和,因为设备是以串行方式提供请求的。但对于并行服务请求的设备,如 RAID 阵列和现代 SSD,这个数字并不反映其性能极限。
这些数据是如何从 /proc/diskstats
得出的,因此相应的 Prometheus 查询也是什么?答案就在源代码中,我在这里做了转译。
r/s
: 字段 1 的增加率.
rate(node_disk_reads_completed_total[*])
单位: 速率(每秒操作数)w/s
: 字段 5 的增加率.
rate(node_disk_writes_completed_total[*])
单位: 速率(每秒操作数)rkB/s
:
rate(node_disk_read_bytes_total[*])
单位: 字节/秒wkB/s
:
rate(node_disk_written_bytes_total[*])
单位: 字节/秒rrqm/s
:
rate(node_disk_reads_merged_total[*])
单位: 速率(每秒操作数)wrqm/s
:
rate(node_disk_writes_merged_total[*])
单位: 速率(每秒操作数)%rrqm
[^2]:
rate(node_disk_reads_merged_total[*]) / (rate(node_disk_reads_merged_total[*] + rate(node_disk_reads_completed_total[*]))
单位: 没有度量单位(0-1分)%wrqm
[^2]:
rate(node_disk_writes_merged_total[*]) / (rate(node_disk_writes_merged_total[*] + rate(node_disk_writes_completed_total[*]))
单位: 没有度量单位(0-1分)r_await
[^2]:
rate(node_disk_read_time_seconds_total[*]) / rate(node_disk_reads_completed_total[*])
单位: 秒w_await
[^2]:
rate(node_disk_write_time_seconds_total[*]) / rate(node_disk_writes_completed_total[*])
单位: 秒aqu-sz
:
rate(node_disk_io_time_weighted_seconds_total[*])
单位: 没有度量单位(排队操作的数量)rareq-sz
[^2]:
rate(node_disk_read_bytes_total[*]) / rate(node_disk_reads_completed_total[*])
单位: 字节wareq-sz
[^2]:
rate(node_disk_written_bytes_total[*]) / rate(node_disk_writes_completed_total[*])
单位: 字节%util
:
rate(node_disk_io_time_seconds_total[*])
单位: 没有度量单位(分数0-1)
我使用[*]
来代表在这些查询中使用的时间范围。如何选择合适的时间范围,可能是另一篇文章的主题,但现在,选择一个至少是你的采样间隔两倍的范围即可。也就是说,如果你以 1 分钟的间隔取样,时间范围必须至少是[2m]
才能成功地计算出比率。在Grafana 7.2 或更高版本中,使用 [$__rate_interval]
,并确保你在 Grafana 数据源中设置了正确的采样率。
解释结果
除了明显的吞吐量数字,每秒的操作数和每秒写入/读取的字节数,还有几个指标特别重要。
aqu-sz:平均队列大小
这表明等待服务的平均操作数量。请注意,一些设备,如 SSD,需要有多个未处理的请求,以达到最大的吞吐量:例如,一个有 8 个内部控制器通道的 SSD 只有在至少有 8 个未处理的并发请求时才能达到全吞吐量。这在某种程度上也适用于硬盘,如果有多个未处理的请求,硬盘就能优化其磁头在盘面上的移动。
这个值是通过获取 node_disk_io_time_weighted_seconds_total
的增加率来精确计算的。
这与 node_disk_io_now
不同,node_disk_io_now
给出的是瞬时队列深度。假设你每 5 秒轮询一次node_exporter;那么 node_disk_io_now
给你的是在采样瞬间在队列中的项目数量。在不同的毫秒时刻这个值会有很大的变化,所以采样值可能是非常嘈杂的。这就是 node_disk_io_time_weighted_seconds_total
指标存在的原因;通过将队列深度乘以队列处于该深度的总时间,并将其相加,通过该指标的增加率得到特定时间段的平均队列深度。
r_await / w_await: 服务时间
这些是分别为每个读请求和写请求提供服务所需的平均时间。如果这些值变得很高,那么说明应用程序正在经历 I/O 延迟,因为在排队等待其他请求。
rareq-sz 和 wareq-sz:平均请求大小
这些对于理解 I/O 模式很有用。应用程序可能混合了小的传输(如 4 KB)或大的传输(如 512 KB);平均数让你了解哪种传输占主导地位。较大的传输更有效率,特别是对于机械硬盘。
%util: 利用率
如果这个值低于 100%,那么设备在某些时间是完全空闲的。这意味着肯定有空余的容量。
然而,如果这个值是 100%,也不一定意味着设备已经饱和了。正如前面所解释的,一些设备需要多个待处理的 I/O 请求来提供其全部吞吐量。因此,一个可以处理(比如)8 个并发请求的设备,在只有 1 或 2 个并发请求的情况下,仍然可以显示 100% 的利用率。在这种情况下,它仍然有很多东西可以提供。你需要看一下吞吐量(kB/s),每秒的操作数,以及队列深度和服务时间,以衡量它被使用的程度。
常见错误
Prometheus 有很多现成的 Grafana 仪表盘,但很多都把这些统计数据弄错了。要么是使用了错误的查询,要么是更常见的标注了错误的图形或是显示错误单位。
作为一个例子,我将以 1860 仪表盘 “Node Exporter Full” 为例。我一直在使用它,并强烈推荐它。
在写这篇文章的时候,其中一个面板被标记为 “磁盘 I/O 加权”,并显示单位为毫秒。
PromQL 查询是:
1 | rate(node_disk_io_time_weighted_seconds_total{instance="$node",job="$job"}[5m]) |
你可能认出这是在查询 “aqu-sz”。问题是什么呢?
首先,Y 轴被标记为 “时间”,并且有时间单位。这是不对的。
原始数据的度量单位是(队列深度*秒),所以它的尺寸是 “秒”。但是,你在这个度量中采取的是一个增加率。因此,其单位是 “秒/秒”,是无维度的。这个值代表了采样区间内的平均队列深度,这只是一个数字。
其次,该图形名为 “磁盘 I/O 加权”。现在,基础指标不是 I/O 操作的数量,而是 I/O 时间(由队列深度加权)。应该说这个图形是 “磁盘 I/O 时间加权” 吗?不是的,因为当你对其进行加权的时候,你已经得到了队列深度。所以一个更好的名字应该是 “平均队列深度”(或大小)。
一旦做了这些修正,该图就更有意义了:它显示了每个数据点所涵盖的时间间隔内未完成的平均 I/O 操作数。
同样,还有一张标有 “I/O 操作的所需时间” 的图表 - 同样,Y 轴以秒为单位标示。
显示的数值范围在 0s 和 1s 之间。它的 PromQL 查询如下:
1 | rate(node_disk_io_time_seconds_total{instance="$node",job="$job"}[5m]) |
事实上,这就是之前显示的 “%util” 的查询。因为这是一个变化率,显示的值实际上是每秒 I/O 活动的秒数。所以它最好显示为一个分数或百分比,而不是时间。再一次,对轴的图例和单位做一个简单的改变就能使它更清晰。
另一张标有 “磁盘 R/W 时间” 的图表显示了以下指标:
1 | rate(node_disk_read_time_seconds_total{...}[5m]) |
不幸的是,这些指标并不是很有意义。尽管被标记为秒,但它们实际上又是无尺寸的(每秒的秒数)。如果你的磁盘每秒进行 100 次读取操作,每次需要 20ms,那么显示的数值是 “2秒”,但这只是意味着平均有2次读取操作在同时进行。
更有用的是使用 r_await
和 w_await
的查询:
1 | rate(node_disk_read_time_seconds_total{...}[5m]) / rate(node_disk_reads_completed_total{...}[5m]) |
这些查询的维度是 “秒”,分别给出了读和写请求的平均服务时间,在这个例子中,图表会显示 “20ms”,而这更有趣。
更新:仪表板的作者已经大方地接受了这些建议并且更新了图表。谢谢你,Ricardo!
总结
希望这篇文章能帮助你理解 node_exporter
的磁盘指标,这些指标与来自 /proc/diskstats
的原始内核数据的关系,如何用它们创建有用的 Prometheus 查询,以及如何解释结果。
[^1] 严格来说,这是以 KB(1024 B)为单位。磁盘传输是以整个扇区为单位,也就是 512 B 或 4096 B,所以这是磁盘传输速度的传统单位。然而,尴尬的是,磁盘存储容量通常是以 10 次方为单位(例如,1 MB = 1,000,000 B),网络传输速度也是如此(例如,1 Mb/s = 1,000,000 b/s)。
Prometheus 通过转换为字节来回避这个问题。你如何缩放你的图表由你决定;Grafana 支持两种选择,对 10 的幂数称之为 “SI”,对 2 的幂数称之为 “IEC”。
[^2] 在 iostat
中,当分母为 0 时,即设备空闲时,程序逻辑将强制答案为 0。用所示的 Prometheus 查询,你最终会得到 NaN 或无穷大。