首先介绍struct disk_stats
的字段,接着介绍如何基于这些字段生成/proc/diskstats,然后介绍如何基于/proc/diskstats生成iostat的输出。本文基于linux kernel 3.19.8。
struct disk_stats (1)
这个结构体定义在include/linux/genhd.h中。它针对一个part(它可能代表一个分区也可能代表一整个disk),统计从系统启动到当前时刻的所有read/write请求。
1 | struct disk_stats { |
sectors[2]
: read/write扇区的数量;ios[2]
: read/write请求数;merges[2]
: read/write请求的合并次数;ticks[2]
: read/write请求从初始化到完成消耗的jiffies累计;io_ticks
: 该分区上存在请求(不管是read还是write)的jiffies累计;time_in_queue
: 该分区上存在的请求数量(不管是read还是write)与逝去jiffies的加权累计;
sectors字段 (1.1)
sectors
字段是在请求结束阶段统计的:
1 | void blk_account_io_completion(struct request *req, unsigned int bytes) |
part_stat_add
是一个macro,它累加part的struct disk_stats
的某个字段。注意,如果part->partno
为0,那么这个part其实代表的是一整个disk;否则part->partno
不为0,这个part代表的是disk的一个分区,在这种情况下,还要累加整个disk的同一字段(part_to_disk((part))
得到disk,其part0
字段就是代表整个disk的part)。其定义如下:
1 |
|
rq_data_dir(req)
拿到req的方向(direction),即read(0)还是write(1)。bytes >> 9
是根据字节数计算扇区数。part_stat_add(cpu, part, sectors[rw], bytes >> 9)
就是做相应的累加。
ios字段 (1.2)
ios
字段也是在请求结束阶段统计的。请求结束时的调用是这样的:
1 | blk_end_request |
如1.1节所述,sectors
的统计是在blk_account_io_completion
中完成的。而ios
的统计是在blk_account_io_done
中完成的:
1 | void blk_account_io_done(struct request *req) |
part_stat_inc
是一个macro,调用1.1节中介绍过的part_stat_add
。它完成的工作是给part的struct disk_stats
某个字段(这里是ios
字段)加1;当然,如果part代表的是一个分区,它还会给disk的同一字段加1。
1 |
|
ticks字段 (1.3)
ticks
字段也是在前述blk_account_io_done
函数中统计的。首先通过duration = jiffies - req->start_time
计算请求从初始化到完成的jiffies,然后通过part_stat_add
累加到part的ticks
(若该part代表的是分区,还会累加到disk的ticks
)。req->start_time
是在blk_rq_init
中设定的:
1 | void blk_rq_init(struct request_queue *q, struct request *rq) |
io_ticks字段和time_in_queue字段 (1.4)
io_ticks
和ticks
关系不大:如1.3节所述,后者是各个read/write请求从初始化(blk_rq_init
)到完成经历的jiffies的累计;前者是part上存在请求(不管是read还write)的时间(jiffies)的累计。怎么理解呢?在part上的请求的数量发生变化的地方(如请求开始、结束和merge的地方),去看刚刚逝去的这一段时间(jiffies)里part上是不是存在请求。若存在,这段jiffies就累加到io_ticks
上;若不存在,则不累加。
反而io_ticks
和time_in_queue
的关系更大:time_in_queue
是分区上存在的请求数量与jiffies的加权累计。什么意思呢?和统计io_ticks
一样,还是在part上的请求数量发生变化的地方,去看刚刚逝去的这一段时间(jiffies)里part上是不是存在请求。不同的是,若存在请求,则把(存在的请求数量 * jiffies)累加到time_in_queue
,否则不累加。
大致可以这么理解:io_ticks
更注重server忙的时间;time_in_queue
更注重client等待的时间。比如超市收银员,我们想看他的繁忙程度:从他早晨上班开始,io_ticks
统计的是结账队列不空的时间总和;time_in_queue
统计的是所有顾客排队结账消耗的时间总和。
这两者的统计都是在part_round_stats
中完成的。这个函数在part上的请求数量发生变化的时候被调用(如前面的blk_account_io_done
函数)。
1 | void part_round_stats(int cpu, struct hd_struct *part) |
和前述part_stat_add
一样,若part代表的是一个分区,则不但要为分区作统计,而且还要为它所在的disk作统计。具体统计的过程是这样的:
1 | static void part_round_stats_single(int cpu, struct hd_struct *part, |
我们看这段代码,每次被调用时:
- 计算距离上次被调用逝去的时间(
now - part->stamp
);
- 计算距离上次被调用逝去的时间(
- 看是否存在请求(if (inflight));
- 若存在,则
time_in_queue
累加上(请求数*逝去时间);io_ticks
累加上逝去时间;
- 若存在,则
- 更新被调用时间,为下次被调用做准备(
part->stamp = now
);
- 更新被调用时间,为下次被调用做准备(
存在的定义是:part上的in_flight
大于0。in_flight
是在这两个函数中完成的:
1 | static inline void part_inc_in_flight(struct hd_struct *part, int rw) |
很明显,这两个函数分别是增加和减小part上的in_flight
值(当part代表分区时,也会对disk做同样的统计)。part_dec_in_flight
在前面的blk_account_io_done
中被调用。part_inc_in_flight
在blk_account_io_start
中被调用。blk_account_io_start
和blk_account_io_done
是对称的,一个在请求开始阶段,一个在请求结束阶段。
在blk_account_io_start
中,我们只看new_io
为true的情况(为false的情况见下文1.5节),除去异常分成相当直观:
1 | void blk_account_io_start(struct request *rq, bool new_io) |
总结来说:io_ticks
和time_in_queue
的统计是这样的:
- 在一个请求产生的时候(part的请求数量发生变化):1.调用
part_round_stats
(是否把最近这一段时间累计到io_ticks
和time_in_queue
);2.in_flight++
; - 在一个请求结束的时候(part的请求数量发生变化):1.调用
part_round_stats
(是否把最近这一段时间累计到io_ticks
和time_in_queue
);2.in_flight--
;
这里需要强调一点:in_flight
是进入elevator的数量。只要elevator不空,io_ticks
就累计。所以io_ticks
代表的是elevator不空的时间。也就是说,基于它得到的磁盘繁忙程度(iostat的util,见下文)其实不能精确代表物理硬件的繁忙程度,若把elevator往下(包含elevator)看成一个黑盒的话,它代表的是这个黑盒的繁忙程度。
merges字段 (1.5)
merges
字段在blk_account_io_start
函数中统计(见1.4节):new_io
为false,表示本请求不是一个新请求,而是一个合并的请求,所以累加merges,不难理解。bio_attempt_back_merge
和bio_attempt_front_merge
在合并成功的时候,以new_io
为false来调用本函数。