Disk I/O stalls can make a Linux system feel slow even when CPU usage, memory pressure, and network traffic look ordinary. Showing live disk activity while the slowdown is happening makes it easier to confirm whether storage is the bottleneck or just where the symptoms surface.
Linux exposes block-device counters through /proc/diskstats and per-task I/O accounting through the kernel task statistics interface. iostat reads the device side of that data so you can see throughput, queue depth, wait time, and utilization per disk, while pidstat -d shows which active processes are reading or writing data during the same interval.
The flow below was verified on Ubuntu 24.04 with the sysstat package installed. Use iostat -y to skip the first report, which is an average since boot, and use pidstat -p ALL when you want a stable full process list during short bursts of disk activity. If pidstat prints Requested activities not available, the running kernel does not expose per-task I/O accounting in that environment.
Related: How to check disk errors in Linux
Related: How to check CPU I/O wait in Linux
$ lsblk -d -o NAME,SIZE,TYPE,MOUNTPOINTS NAME SIZE TYPE MOUNTPOINTS loop0 64M loop loop1 256M loop ##### snipped ##### vda 1.8T disk vdb 622.9M disk
Ignore pseudo devices such as loop entries or empty nbd devices when they appear. If iostat or pidstat is missing on Ubuntu or Debian, install the sysstat package with sudo apt install sysstat before sampling.
$ iostat -dx -y 1 3 Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util ##### snipped ##### vda 0.00 0.00 0.00 0.00 0.00 0.00 7387.00 70640.00 10279.00 58.19 0.10 9.56 239.00 6380.00 0.00 0.00 0.05 26.69 4913.00 0.07 1.09 67.40 vdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util ##### snipped ##### vda 0.00 0.00 0.00 0.00 0.00 0.00 9683.00 87016.00 12231.00 55.81 0.09 8.99 2.00 40.00 0.00 0.00 0.00 20.00 6254.00 0.07 1.30 70.70 vdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
r/s and w/s show request rates, rkB/s and wkB/s show throughput, await shows average wait time, aqu-sz shows the average queue depth, and \%util shows how busy the device stayed during the interval.
$ iostat -dx -y vda 1 3 Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util vda 0.00 0.00 0.00 0.00 0.00 0.00 9588.12 87631.68 12398.02 56.39 0.08 9.14 2065.35 1331489.11 0.00 0.00 0.07 644.68 6255.45 0.07 1.41 69.90 Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util vda 0.00 0.00 0.00 0.00 0.00 0.00 8064.36 75425.74 10792.08 57.23 0.09 9.35 1.98 7.92 0.00 0.00 0.00 4.00 5376.24 0.08 1.11 62.48
Replace vda with the disk name from lsblk. Sustained high \%util together with rising await or aqu-sz is a stronger sign of storage pressure than a brief one-interval spike.
Related: How to check CPU I/O wait in Linux
$ pidstat -d -p ALL 1 3 05:11:45 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command ##### snipped ##### 05:11:46 0 3544 0.00 14264.00 0.00 0 dd ##### snipped ##### 05:11:47 0 3544 0.00 12684.00 0.00 0 dd ##### snipped ##### Average: 0 3544 0.00 13461.33 0.00 0 dd
kB_rd/s and kB_wr/s show per-task throughput, while iodelay reflects time spent waiting for block I/O and swap-in completion. pidstat shows only active tasks by default, so -p ALL keeps the report stable when the workload is short-lived.
$ pidstat -d -G dd 1 3 05:12:20 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 05:12:21 0 3545 0.00 11112.87 0.00 0 dd 05:12:21 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 05:12:22 0 3545 0.00 10392.00 0.00 0 dd 05:12:22 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 05:12:23 0 3545 0.00 10748.00 0.00 0 dd Average: UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command Average: 0 3545 0.00 10752.16 0.00 0 dd
Replace dd with a command name or regular expression such as rsync, postgres, or mysqld when you already know which workload you want to follow.
A busy disk in iostat should line up with one or more active readers or writers in pidstat during the same sample window. If the device stays busy but pidstat shows nothing useful, the pressure may be coming from a short-lived workload, kernel threads, the storage backend below the guest, or an environment where per-task I/O accounting is unavailable.