iostat源码解析
iostat命令是报告cpu的统计信息和磁盘的i/o统计信息。iostat命令通过观察存储设备实际的工作时间和它们的平均传输率来监控系统的i/o负载。这个不需要root权限,数据来源可以直接通过访问procfs获取。基本用法和输出的基本含义iostat -d -x 1#表示显示设备状态,显示扩展信息,每秒输出一次iostat指标解读性能指标含义提示Device...
iostat命令是报告cpu的统计信息和磁盘的i/o统计信息。iostat命令通过观察存储设备实际的工作时间和它们的平均传输率来监控系统的i/o负载。
这个不需要root权限,数据来源可以直接通过访问procfs获取。
基本用法和输出的基本含义
iostat -d -x 1#表示显示设备状态,显示扩展信息,每秒输出一次
iostat指标解读
性能指标 | 含义 | 提示 |
---|---|---|
Device | 显示设备或者分区的Name | 这些设备可以在/dev下找到 |
r/s | 每秒发送给磁盘的请求数 | 合并后的请求数 |
w/s | 每秒发送给磁盘的请求数 | 合并后的请求数 |
rkB/s | 每秒从磁盘读取的数据量 | 单位为kB |
wkB/s | 每秒向磁盘写入的数据量 | 单位为kB |
rrqm/s | 每秒合并的读请求数 | %rrqm表示合并读请求的百分比 |
wrqm/s | 每秒合并的写请求数 | %wrqm表示合并写请求的百分比 |
r_await | 读请求处理完成等待时间 | 包括队列中的等待时间和设备实际处理的时间,单位ms |
w_await | 写请求处理完成等待时间 | 包括队列中的等待时间和设备实际处理的时间,单位ms |
aqu-sz | 平均请求队列长度 | 旧版本中为avgqu-sz |
rareq-sz | 平均读请求大小 | 单位为kB |
wareq-sz | 平均写请求大小 | 单位为kB |
svctm | 处理IO请求所需的平均时间 (不包括等待时间) | 单位为毫秒 |
%util | 磁盘处理IO的时间百分比 | 即使用率,由于可能存在并行IO,100%不表明磁盘IO饱和 |
iostat数据来源diskstats
这里的数据来源指的是/proc/diskstats
iostat是以/proc/diskstats为基础计算出来的,这是前提。而且很多查IO的方法都是通过/proc/diskstats为基础计算出来的。
由于/proc/diskstats并未把队列等待时间和硬盘处理时间分开,所以凡是以它为基础的工具都不可能分别提供disk service time以及与queue有关的值。
这里举sda
为例
8 0 sda 63147 32179 3914580 61983 287312 216951 7881680 434138 0 190628 267912 0 0 0 0
每个参数对应的含义:
前3个字段分别代表major, minor, device; 后面的15个字段含义如下:
index | Name | units | description |
---|---|---|---|
1 | read I/Os | requests | number of read I/Os processed |
2 | read merges | requests | number of read I/Os merged with in-queue I/O |
3 | read sectors | sectors | number of sectors read |
4 | read ticks | milliseconds | total wait time for read requests |
5 | write I/Os | requests | number of write I/Os processed |
6 | write merges | requests | number of write I/Os merged with in-queue I/O |
7 | write sectors | sectors | number of sectors written |
8 | write ticks | milliseconds | total wait time for write requests |
9 | in_flight | requests | number of I/Os currently in flight |
10 | io_ticks | milliseconds | total time this block device has been active |
11 | time_in_queue | milliseconds | total wait time for all requests |
12 | discard I/Os | requests | number of discard I/Os processed |
13 | discard merges | requests | number of discard I/Os merged with in-queue I/O |
14 | discard sectors | sectors | number of sectors discarded |
15 | discard ticks | milliseconds | total wait time for discard requests |
这里面大部分字段都是很容易理解的,稍微难理解的在于io_ticks。初看之下,明明已经有了rd_ticks和wr_ticks 为什么还需一个io_ticks。注意rd_ticks和wr_ticks是把每一个IO消耗时间累加起来,但是硬盘设备一般可以并行处理多个IO,因此,rd_ticks和wr_ticks之和一般会比自然时间(wall-clock time)要大。而io_ticks 不关心队列中有多少个IO在排队,它只关心设备有IO的时间。即不考虑IO有多少,只考虑IO有没有。在实际运算中,in_flight不是0的时候保持计时,而in_flight 等于0的时候,时间不累加到io_ticks。
内核中的数据结构
path: root/include/linux/blk_types.h
enum stat_group {
STAT_READ,
STAT_WRITE,
STAT_DISCARD,
NR_STAT_GROUPS
};
path: root/include/linux/genhd.h
struct disk_stats {
u64 nsecs[NR_STAT_GROUPS];
unsigned long sectors[NR_STAT_GROUPS];
unsigned long ios[NR_STAT_GROUPS];
unsigned long merges[NR_STAT_GROUPS];
unsigned long io_ticks;
unsigned long time_in_queue;
local_t in_flight[2];
};
path: root/block/genhd.c
static int diskstats_show(struct seq_file *seqf, void *v)
{
struct gendisk *gp = v;
struct disk_part_iter piter;
struct hd_struct *hd;
char buf[BDEVNAME_SIZE];
unsigned int inflight;
/*
if (&disk_to_dev(gp)->kobj.entry == block_class.devices.next)
seq_puts(seqf, "major minor name"
" rio rmerge rsect ruse wio wmerge "
"wsect wuse running use aveq"
"\n\n");
*/
disk_part_iter_init(&piter, gp, DISK_PITER_INCL_EMPTY_PART0);
while ((hd = disk_part_iter_next(&piter))) {
inflight = part_in_flight(gp->queue, hd);
seq_printf(seqf, "%4d %7d %s "
"%lu %lu %lu %u "
"%lu %lu %lu %u "
"%u %u %u "
"%lu %lu %lu %u\n",
MAJOR(part_devt(hd)), MINOR(part_devt(hd)),
disk_name(gp, hd->partno, buf),
part_stat_read(hd, ios[STAT_READ]),
part_stat_read(hd, merges[STAT_READ]),
part_stat_read(hd, sectors[STAT_READ]),
(unsigned int)part_stat_read_msecs(hd, STAT_READ),
part_stat_read(hd, ios[STAT_WRITE]),
part_stat_read(hd, merges[STAT_WRITE]),
part_stat_read(hd, sectors[STAT_WRITE]),
(unsigned int)part_stat_read_msecs(hd, STAT_WRITE),
inflight,
jiffies_to_msecs(part_stat_read(hd, io_ticks)),
jiffies_to_msecs(part_stat_read(hd, time_in_queue)),
part_stat_read(hd, ios[STAT_DISCARD]),
part_stat_read(hd, merges[STAT_DISCARD]),
part_stat_read(hd, sectors[STAT_DISCARD]),
(unsigned int)part_stat_read_msecs(hd, STAT_DISCARD),
);
}
disk_part_iter_exit(&piter);
return 0;
}
用户态iostat的数据结构
用户态程序打开/proc/diskstats
文件,将数据填入到struct io_stats
的结构中。
/*
* Structures for I/O stats.
* These are now dynamically allocated.
*/
struct io_stats {
/* # of sectors read */
unsigned long rd_sectors __attribute__ ((aligned (8)));
/* # of sectors written */
unsigned long wr_sectors __attribute__ ((packed));
/* # of sectors discarded */
unsigned long dc_sectors __attribute__ ((packed));
/* # of read operations issued to the device */
unsigned long rd_ios __attribute__ ((packed));
/* # of read requests merged */
unsigned long rd_merges __attribute__ ((packed));
/* # of write operations issued to the device */
unsigned long wr_ios __attribute__ ((packed));
/* # of write requests merged */
unsigned long wr_merges __attribute__ ((packed));
/* # of discard operations issued to the device */
unsigned long dc_ios __attribute__ ((packed));
/* # of discard requests merged */
unsigned long dc_merges __attribute__ ((packed));
/* Time of read requests in queue */
unsigned int rd_ticks __attribute__ ((packed));
/* Time of write requests in queue */
unsigned int wr_ticks __attribute__ ((packed));
/* Time of discard requests in queue */
unsigned int dc_ticks __attribute__ ((packed));
/* # of I/Os in progress */
unsigned int ios_pgr __attribute__ ((packed));
/* # of ticks total (for this device) for I/O */
unsigned int tot_ticks __attribute__ ((packed));
/* # of ticks requests spent in queue */
unsigned int rq_ticks __attribute__ ((packed));
};
采集数据到显示数据的计算
通过获取的数据,我们可以直接得到以下信息。
r/s
w/s
rkB/s
wkB/s
rrqm/s
wrqm/s
这几项的计算是非常简单的,就是采样两次,后一次的值减去前一次的值,然后除以时间间隔,得到平均值即可。因为这些/proc/diskstats中对应的值都是累加的,后一次减去前一次,即得到采样时间间隔内的新增量。不赘述。
r_wait和w_wait的计算
xios.r_await = (ioi->rd_ios - ioj->rd_ios) ?
(ioi->rd_ticks - ioj->rd_ticks) /
((double) (ioi->rd_ios - ioj->rd_ios)) : 0.0;
xios.w_await = (ioi->wr_ios - ioj->wr_ios) ?
(ioi->wr_ticks - ioj->wr_ticks) /
((double) (ioi->wr_ios - ioj->wr_ios)) : 0.0;
r _ a w a i t = ( 间 隔 期 间 所 有 读 I O 花 费 的 时 间 ) / ( 间 隔 期 间 读 请 求 的 个 数 ) w _ a w a i t = ( 间 隔 期 间 所 有 写 I O 花 费 的 时 间 ) / ( 间 隔 期 间 写 请 求 的 个 数 ) r\_await = (间隔期间所有读IO花费的时间)/(间隔期间读请求的个数) \\ w\_await = (间隔期间所有写IO花费的时间)/(间隔期间写请求的个数) r_await=(间隔期间所有读IO花费的时间)/(间隔期间读请求的个数)w_await=(间隔期间所有写IO花费的时间)/(间隔期间写请求的个数)
aqu-sz 平均队列深度的计算
//其中rq_ticks即diskstats中的time_in_queue,注意这里的1000是ms->s
#define S_VALUE(m,n,p) (((double) ((n) - (m))) / (p) * 100)
S_VALUE(ioj->rq_ticks, ioi->rq_ticks, itv) / 1000.0);
这里看下内核的实现:
path: root/drivers/md/dm-stats.c
difference = now - shared->stamp;
if (!difference)
return;
in_flight_read = (unsigned)atomic_read(&shared->in_flight[READ]);
in_flight_write = (unsigned)atomic_read(&shared->in_flight[WRITE]);
if (in_flight_read)
p->io_ticks[READ] += difference;
if (in_flight_write)
p->io_ticks[WRITE] += difference;
if (in_flight_read + in_flight_write) {
p->io_ticks_total += difference;
p->time_in_queue += (in_flight_read + in_flight_write) * difference;
}
shared->stamp = now;
举个栗子:
当第一个IO完成的时候,队列中250个IO,250个IO都等了4ms,即time_in_queue + = (250 * 4) ,当第二个IO完成的时候,time_in_queue += (249 * 4),当所有IO都完成的时候,time_in_queue = 4*(250+249+248….+1)
根据 t i m e _ i n _ q u e u e / 1000 time\_in\_queue/1000 time_in_queue/1000,得到了平均队列长度。
$$
time_in_queue += \Delta t_n * inflight_n \
aqu_sz = 间隔时间内inflight的期望值 = \frac{\Delta t_i * inflight_i + … + \Delta t_j * inflight_j}{\Delta t_i + … + \Delta t_i}
$$
rareq-sz & wareq-sz 平均请求的sector大小
xios.rarqsz = (ioi->rd_ios - ioj->rd_ios) ?
(ioi->rd_sectors - ioj->rd_sectors) / ((double) (ioi->rd_ios - ioj->rd_ios)) :
0.0;
xios.warqsz = (ioi->wr_ios - ioj->wr_ios) ?
(ioi->wr_sectors - ioj->wr_sectors) / ((double) (ioi->wr_ios - ioj->wr_ios)) :
0.0;
r a r e q _ s z = ( 间 隔 期 间 读 取 的 s e c t o r 增 长 量 ) / ( 间 隔 期 间 读 请 求 的 个 数 ) w a r e q _ s z = ( 间 隔 期 间 写 入 的 s e c t o r 增 长 量 ) / ( 间 隔 期 间 写 请 求 的 个 数 ) rareq\_sz = (间隔期间读取的sector增长量)/(间隔期间读请求的个数) \\ wareq\_sz = (间隔期间写入的sector增长量)/(间隔期间写请求的个数) rareq_sz=(间隔期间读取的sector增长量)/(间隔期间读请求的个数)wareq_sz=(间隔期间写入的sector增长量)/(间隔期间写请求的个数)
%util 磁盘设备饱和度(数据不准确)
path: root/drivers/md/dm-stats.c
//其中的io_ticks_total即是队列非空的总时间
difference = now - shared->stamp;
if (!difference)
return;
in_flight_read = (unsigned)atomic_read(&shared->in_flight[READ]);
in_flight_write = (unsigned)atomic_read(&shared->in_flight[WRITE]);
if (in_flight_read)
p->io_ticks[READ] += difference;
if (in_flight_write)
p->io_ticks[WRITE] += difference;
if (in_flight_read + in_flight_write) {
p->io_ticks_total += difference;
p->time_in_queue += (in_flight_read + in_flight_write) * difference;
}
shared->stamp = now;
最简单的例子是,某硬盘处理单个IO请求需要0.1秒,有能力同时处理10个。但是当10个请求依次提交的时候,需要1秒钟才能完成这10%的请求,,在1秒的采样周期里,%util达到了100%。但是如果10个请一次性提交的话, 硬盘可以在0.1秒内全部完成,这时候,%util只有10%。
在上面的例子中,一秒中10个IO,即IOPS=10的时候,%util就达到了100%,这并不能表明,该盘的IOPS就只能到10,事实上,纵使%util到了100%,硬盘可能仍然有很大的余力处理更多的请求,即并未达到饱和的状态。
那么有没有一个指标用来衡量硬盘设备的饱和程度呢。很遗憾,iostat没有一个指标可以衡量磁盘设备的饱和度。
明明已经有了rd_ticks和wr_ticks 为什么还需一个io_ticks。注意rd_ticks和wr_ticks是把每一个IO消耗时间累加起来,但是硬盘设备一般可以并行处理多个IO,因此,rd_ticks和wr_ticks之和一般会比自然时间(wall-clock time)要大。而io_ticks 不关心队列中有多少个IO在排队,它只关心设备有IO的时间。即不考虑IO有多少,只考虑IO有没有。在实际运算中,in_flight不是0的时候保持计时,而in_flight 等于0的时候,时间不累加到io_ticks。
svctm 处理IO请求所需的平均时间
double tput = ((double) (sdc->nr_ios - sdp->nr_ios)) * HZ / itv;
xds->util = S_VALUE(sdp->tot_ticks, sdc->tot_ticks, itv);
xds->svctm = tput ? xds->util / tput : 0.0;
对于iostat这个功能而言,%util固然会给人带来一定的误解和苦扰,但是svctm给人带来的误解更多。一直以来,人们希望了解块设备处理单个IO的service time,这个指标直接地反应了硬盘的能力。
但是service time和iostat无关,iostat没有任何一个参数能够提供这方面的信息。这个值其实并没有什么意义,事实上,这个值不是独立的,它是根据其他值计算出来的。
更多推荐
所有评论(0)