Currently io_ticks is approximated by adding one at each start and end of requests if jiffies counter has changed. This works perfectly for requests shorter than a jiffy or if one of requests starts/ends at each jiffy. If disk executes just one request at a time and they are longer than two jiffies then only first and last jiffies will be accounted. Fix is simple: at the end of request add up into io_ticks jiffies passed since last update rather than just one jiffy. Example: common HDD executes random read 4k requests around 12ms. fio --name=test --filename=/dev/sdb --rw=randread --direct=1 --runtime=30 & iostat -x 10 sdb Note changes of iostat's "%util" 8,43% -> 99,99% before/after patch: Before: Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sdb 0,00 0,00 82,60 0,00 330,40 0,00 8,00 0,96 12,09 12,09 0,00 1,02 8,43 After: Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sdb 0,00 0,00 82,50 0,00 330,00 0,00 8,00 1,00 12,10 12,10 0,00 12,12 99,99 Now io_ticks does not loose time between start and end of requests, but for queue-depth > 1 some I/O time between adjacent starts might be lost. For load estimation "%util" is not as useful as average queue length, but it clearly shows how often disk queue is completely empty.
Unified · Split
| @@ -100,7 +100,7 @@ Field 10 -- # of milliseconds spent doing I/Os (unsigned int) · 99 unmodified lines | |||
| 100 | 100 | ||
| 101 | Since 5.0 this field counts jiffies when at least one request was | 101 | Since 5.0 this field counts jiffies when at least one request was |
| 102 | started or completed. If request runs more than 2 jiffies then some | 102 | started or completed. If request runs more than 2 jiffies then some |
| 103 | I/O time will not be accounted unless there are other requests. | 103 | I/O time might be not accounted in case of concurrent requests. |
| 104 | 104 | ||
| 105 | Field 11 -- weighted # of milliseconds spent doing I/Os (unsigned int) | 105 | Field 11 -- weighted # of milliseconds spent doing I/Os (unsigned int) |
| 106 | This field is incremented at each I/O start, I/O completion, I/O | 106 | This field is incremented at each I/O start, I/O completion, I/O |
| @@ -143,6 +143,9 @@ are summed (possibly overflowing the unsigned long variable they are · 36 unmodified lines | |||
| 143 | summed to) and the result given to the user. There is no convenient | 143 | summed to) and the result given to the user. There is no convenient |
| 144 | user interface for accessing the per-CPU counters themselves. | 144 | user interface for accessing the per-CPU counters themselves. |
| 145 | 145 | ||
| 146 | Since 4.19 request times are measured with nanoseconds precision and | ||
| 147 | truncated to milliseconds before showing in this interface. | ||
| 148 | |||
| 146 | Disks vs Partitions | 149 | Disks vs Partitions |
| 147 | ------------------- | 150 | ------------------- |
| 148 | 151 | ||
| @@ -1768,14 +1768,14 @@ defer: · 1767 unmodified lines | |||
| 1768 | schedule_work(&bio_dirty_work); | 1768 | schedule_work(&bio_dirty_work); |
| 1769 | } | 1769 | } |
| 1770 | 1770 | ||
| 1771 | void update_io_ticks(struct hd_struct *part, unsigned long now) | 1771 | void update_io_ticks(struct hd_struct *part, unsigned long now, bool end) |
| 1772 | { | 1772 | { |
| 1773 | unsigned long stamp; | 1773 | unsigned long stamp; |
| 1774 | again: | 1774 | again: |
| 1775 | stamp = READ_ONCE(part->stamp); | 1775 | stamp = READ_ONCE(part->stamp); |
| 1776 | if (unlikely(stamp != now)) { | 1776 | if (unlikely(stamp != now)) { |
| 1777 | if (likely(cmpxchg(&part->stamp, stamp, now) == stamp)) { | 1777 | if (likely(cmpxchg(&part->stamp, stamp, now) == stamp)) { |
| 1778 | __part_stat_add(part, io_ticks, 1); | 1778 | __part_stat_add(part, io_ticks, end ? now - stamp : 1); |
| 1779 | } | 1779 | } |
| 1780 | } | 1780 | } |
| 1781 | if (part->partno) { | 1781 | if (part->partno) { |
| @@ -1791,7 +1791,7 @@ void generic_start_io_acct(struct request_queue *q, int op, · 9 unmodified lines | |||
| 1791 | 1791 | ||
| 1792 | part_stat_lock(); | 1792 | part_stat_lock(); |
| 1793 | 1793 | ||
| 1794 | update_io_ticks(part, jiffies); | 1794 | update_io_ticks(part, jiffies, false); |
| 1795 | part_stat_inc(part, ios[sgrp]); | 1795 | part_stat_inc(part, ios[sgrp]); |
| 1796 | part_stat_add(part, sectors[sgrp], sectors); | 1796 | part_stat_add(part, sectors[sgrp], sectors); |
| 1797 | part_inc_in_flight(q, part, op_is_write(op)); | 1797 | part_inc_in_flight(q, part, op_is_write(op)); |
| @@ -1809,7 +1809,7 @@ void generic_end_io_acct(struct request_queue *q, int req_op, · 11 unmodified lines | |||
| 1809 | 1809 | ||
| 1810 | part_stat_lock(); | 1810 | part_stat_lock(); |
| 1811 | 1811 | ||
| 1812 | update_io_ticks(part, now); | 1812 | update_io_ticks(part, now, true); |
| 1813 | part_stat_add(part, nsecs[sgrp], jiffies_to_nsecs(duration)); | 1813 | part_stat_add(part, nsecs[sgrp], jiffies_to_nsecs(duration)); |
| 1814 | part_stat_add(part, time_in_queue, duration); | 1814 | part_stat_add(part, time_in_queue, duration); |
| 1815 | part_dec_in_flight(q, part, op_is_write(req_op)); | 1815 | part_dec_in_flight(q, part, op_is_write(req_op)); |
| @@ -1337,7 +1337,7 @@ void blk_account_io_done(struct request *req, u64 now) · 1336 unmodified lines | |||
| 1337 | part_stat_lock(); | 1337 | part_stat_lock(); |
| 1338 | part = req->part; | 1338 | part = req->part; |
| 1339 | 1339 | ||
| 1340 | update_io_ticks(part, jiffies); | 1340 | update_io_ticks(part, jiffies, true); |
| 1341 | part_stat_inc(part, ios[sgrp]); | 1341 | part_stat_inc(part, ios[sgrp]); |
| 1342 | part_stat_add(part, nsecs[sgrp], now - req->start_time_ns); | 1342 | part_stat_add(part, nsecs[sgrp], now - req->start_time_ns); |
| 1343 | part_stat_add(part, time_in_queue, nsecs_to_jiffies64(now - req->start_time_ns)); | 1343 | part_stat_add(part, time_in_queue, nsecs_to_jiffies64(now - req->start_time_ns)); |
| @@ -1379,7 +1379,7 @@ void blk_account_io_start(struct request *rq, bool new_io) · 35 unmodified lines | |||
| 1379 | rq->part = part; | 1379 | rq->part = part; |
| 1380 | } | 1380 | } |
| 1381 | 1381 | ||
| 1382 | update_io_ticks(part, jiffies); | 1382 | update_io_ticks(part, jiffies, false); |
| 1383 | 1383 | ||
| 1384 | part_stat_unlock(); | 1384 | part_stat_unlock(); |
| 1385 | } | 1385 | } |
| @@ -422,7 +422,7 @@ void part_dec_in_flight(struct request_queue *q, struct hd_struct *part, · 421 unmodified lines | |||
| 422 | void part_inc_in_flight(struct request_queue *q, struct hd_struct *part, | 422 | void part_inc_in_flight(struct request_queue *q, struct hd_struct *part, |
| 423 | int rw); | 423 | int rw); |
| 424 | 424 | ||
| 425 | void update_io_ticks(struct hd_struct *part, unsigned long now); | 425 | void update_io_ticks(struct hd_struct *part, unsigned long now, bool end); |
| 426 | 426 | ||
| 427 | /* block/genhd.c */ | 427 | /* block/genhd.c */ |
| 428 | extern void device_add_disk(struct device *parent, struct gendisk *disk, | 428 | extern void device_add_disk(struct device *parent, struct gendisk *disk, |