Linux 监控之 IO
简单介绍下 Linux 中与 IO 相关的内容。
简介
可以通过如下命令查看与 IO 相关的系统信息。
# tune2fs -l /dev/sda7 ← 读取superblock信息
# blockdev --getbsz /dev/sda7 ← 获取block大小
# tune2fs -l /dev/sda7 | grep "Block size" ← 同上
# dumpe2fs /dev/sda7 | grep "Block size" ← 同上
# stat /boot/ | grep "IO Block" ← 同上
# fdisk -l ← 硬盘的扇区大小(Sector Size)
在 WiKi 中的定义:A “block”, a contiguous number of bytes, is the minimum unit of memory that is read from and written to a disk by a disk driver。
块是文件系统的抽象,而非磁盘的属性,一般是 Sector Size 的倍数;扇区大小则是磁盘的物理属性,它是磁盘设备寻址的最小单元。另外,内核中要求 Block_Size = Sector_Size * (2的n次方),且 Block_Size <= 内存的 Page_Size (页大小)。
磁盘使用空间
实际上是通过 statvfs()
方法查询磁盘数据,可以通过如下命令查看。
$ python -c 'import os; os.statvfs("/")'
其空间占用大致如下。
+--------------------------+----------------+-------------------------------------------------------+
| | | |
+--------------------------+----------------+-------------------------------------------------------+
|<-- f_bavail(non-root) -->|<-- reserved -->|<------------- f_bused=f_blocks-f_bfree -------------->|
|<------------- f_bfree(root) ------------->| |
|<----------------------------------------- f_blocks ---------------------------------------------->|
前者是非 root 用户已经使用的占非 root 用户可用空间百分数;后者是保留给 root 用户以及已经使用磁盘占整个磁盘空间百分数。对于 extN 类的文件系统一般会保留 1%~5% 的磁盘空间给 root 使用,当 reserved 占比较大时会导致两者的计算差较大。
磁盘类型
主要是要获取当前系统使用的什么类型的磁盘 (SCSI、IDE、SSD等),甚至是制造商、机器型号、序列号等信息。
$ dmesg | grep scsi
监控指标
简单列举磁盘监控时常见的指标。
IOPS 每秒IO数
对磁盘来说,一次磁盘的连续读或写称为一次磁盘 IO,当传输小块不连续数据时,该指标有重要参考意义。
Throughput 吞吐量
硬盘传输数据流的速度,单位一般为 MB/s,在传输大块不连续数据的数据,该指标有重要参考作用。
IO平均大小
实际上就是吞吐量除以 IOPS,用于判断磁盘使用模式,一般大于 32K 为顺序读取为主,否则随机读取为主。
Utilization 磁盘活动时间百分比
磁盘处于活动状态 (数据传输、寻道等) 的时间百分比,也即磁盘利用率,一般该值越高对应的磁盘资源争用越高。
Service Time 服务时间
磁盘读写操作执行的时间,对于机械磁盘包括了寻道、旋转、数据传输等,与磁盘性能相关性较高,另外,也受 CPU、内存影响。
Queue Length 等待队列长度
待处理的 IO 请求的数目,注意,如果该磁盘为磁盘阵列虚拟的逻辑驱动器,需要除以实际磁盘数,以获取单盘的 IO 队列。
Wait Time 等待时间
在队列中排队的时间。
iostat 系统级
除了可以通过该命令查看磁盘信息之外,还可以用来查看 CPU 信息,分别通过 -d
和 -c
参数控制;可直接通过 iostat -xdm 1
命令显示磁盘队列的长度等信息。
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.02 1.00 0.99 1.84 0.03 0.04 46.98 0.01 2.44 0.47 3.49 0.25 0.07
其中参数如下:
rrqm/s wrqm/s
读写请求每秒合并后发送给磁盘的请求。
r/s w/s
应用发送给系统的请求数目。
argrq-sz
提交给驱动层IO请求的平均大小(sectors),一般不小于4K,不大于max(readahead_kb, max_sectors_kb);
可用于判断当前的 IO 模式,越大代表顺序,越小代表随机;计算公式如下:
argrq-sz = (rsec + wsec) / (r + w)
argqu-sz Average Queue Size
在驱动层的队列排队的平均长度。
await Average Wait
平均的等待时间,包括了在队列中的等待时间,以及磁盘的处理时间。
svctm(ms) Service Time
请求发送给IO设备后的响应时间,也就是一次磁盘IO请求的服务时间,不过该指标官网说不准确,要取消。
对于单块SATA盘,完全随机读时,基本在7ms左右,既寻道+旋转延迟时间。
%util
一秒内IO操作所占的比例,计算公式是(r/s+w/s)*(svctm/1000),例如采集时间为 1s 其中有 0.8s 在处
理 IO 请求,那么 util 为 80% ;对于一块磁盘,如果没有并发IO的概念,那么这个公式是正确的,但
是对于RAID磁盘组或者SSD来说,这个计算公式就有问题了,就算这个值超过100%,也不代表存储有瓶颈,
容易产生误导。
iostat 统计的是通用块层经过合并 (rrqm/s, wrqm/s) 后,直接向设备提交的 IO 数据,可以反映系统整体的 IO 状况,但是距离应用层比较远,由于系统预读、Page Cache、IO调度算法等因素,很难跟代码中的 write()、read() 对应。
简言之,这是系统级,没办法精确到进程,比如只能告诉你现在磁盘很忙,但是没办法告诉你是那个进程在忙,在忙什么?
/proc/diskstats
该命令会读取 /proc/diskstats
文件,各个指标详细的含义可以参考内核文档 iostats.txt,其中各个段的含义如下。
filed1 rd_ios
成功完成读的总次数;
filed2 rd_merges
合并写完成次数,通过合并提高效率,例如两次4K合并为8K,这样只有一次IO操作;合并操作是由IO Scheduler(也叫 Elevator)负责。
filed3 rd_sectors
成功读过的扇区总次数;
filed4 rd_ticks
所有读操作所花费的毫秒数,每个读从__make_request()开始计时,到end_that_request_last()为止,包括了在队列中等待的时间;
filed5 wr_ios
成功完成写的总次数;
filed6 wr_merges
合并写的次数;
filed7 wr_sectors
成功写过的扇区总次数;
filed8 wr_ticks
所有写操作所花费的毫秒数;
filed9 in_flight
现在正在进行的IO数目,在IO请求进入队列时该值加1,在IO结束时该值减1,注意是在进出队列时,而非交给磁盘时;
filed10 io_ticks
输入/输出操作花费的毫秒数;
filed11 time_in_queue
是一个权重值,当有上面的IO操作时,这个值就增加。
需要注意 io_ticks
与 rd/wr_ticks
的区别,后者是把每一个 IO 所消耗的时间累加在一起,因为硬盘设备通常可以并行处理多个 IO,所以统计值往往会偏大;而前者表示该设备有 IO 请求在处理的时间,也就是非空闲,不考虑 IO 有多少,只考虑现在有没有 IO 操作。在实际计算时,会在字段 in_flight
不为零的时候 io_ticks
保持计时,为 0 时停止计时。
另外,io_ticks
在统计时不考虑当前有几个 IO,而 time_in_queue
是用当前的 IO 数量 (in_flight) 乘以时间,统计时间包括了在队列中的时间以及磁盘处理 IO 的时间。
重要指标
简单介绍下常见的指标,包括了经常误解的指标。
util
这里重点说一下 iostat 中 util 的含义,该参数可以理解为磁盘在处理 IO 请求的总时间,如果是 100% 则表明磁盘一直在处理 IO 请求,这也就意味着 IO 在满负载运行。
对于一块磁盘,如果没有并发 IO 的概念,所以这个公式是正确的,但是现在的磁盘或者对于RAID磁盘组以及SSD来说,这个计算公式就有问题了,就算这个值超过100%,也不代表存储有瓶颈,容易产生误导。
举个简化的例子:某硬盘处理单个 IO 需要 0.1 秒,也就是有能力达到 10 IOPS,那么当 10 个 IO 请求依次顺序提交的时候,需要 1 秒才能全部完成,在 1 秒的采样周期里 %util 达到 100%;而如果 10 个 IO 请求一次性提交的话,0.1 秒就全部完成,在 1 秒的采样周期里 %util 只有 10%。
可见,即使 %util 高达 100%,硬盘也仍然有可能还有余力处理更多的 IO 请求,即没有达到饱和状态。不过遗憾的是现在 iostat 没有提供类似的指标。
在 CentOS 中使用的是 github sysstat,如下是其计算方法。
rw_io_stat_loop() 循环读取
|-read_diskstats_stat() 从/proc/diskstats读取状态
|-write_stats() 输出采集的监控指标
|-write_ext_stat()
|-compute_ext_disk_stats() 计算ext选项,如util
|-write_plain_ext_stat()
关于该参数的代码详细介绍如下。
#define S_VALUE(m,n,p) (((double) ((n) - (m))) / (p) * HZ)
void read_diskstats_stat(int curr)
{
struct io_stats sdev;
... ...
if ((fp = fopen(DISKSTATS, "r")) == NULL)
return;
while (fgets(line, sizeof(line), fp) != NULL) {
/* major minor name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq */
i = sscanf(line, "%u %u %s %lu %lu %lu %lu %lu %lu %lu %u %u %u %u",
&major, &minor, dev_name,
&rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios, &rd_ticks_or_wr_sec,
&wr_ios, &wr_merges, &wr_sec, &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks);
if (i == 14) {
/* Device or partition */
if (!dlist_idx && !DISPLAY_PARTITIONS(flags) &&
!is_device(dev_name, ACCEPT_VIRTUAL_DEVICES))
continue;
sdev.rd_ios = rd_ios;
sdev.rd_merges = rd_merges_or_rd_sec;
sdev.rd_sectors = rd_sec_or_wr_ios;
sdev.rd_ticks = (unsigned int) rd_ticks_or_wr_sec;
sdev.wr_ios = wr_ios;
sdev.wr_merges = wr_merges;
sdev.wr_sectors = wr_sec;
sdev.wr_ticks = wr_ticks;
sdev.ios_pgr = ios_pgr;
sdev.tot_ticks = tot_ticks;
sdev.rq_ticks = rq_ticks;
}
... ...
save_stats(dev_name, curr, &sdev, iodev_nr, st_hdr_iodev);
}
fclose(fp);
}
void write_json_ext_stat(int tab, unsigned long long itv, int fctr,
struct io_hdr_stats *shi, struct io_stats *ioi,
struct io_stats *ioj,