How to show disk activity on Linux

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.

Steps to show disk activity with iostat and pidstat on Linux:

  1. List block devices so the active disk can be identified by name before sampling.
    $ 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.

  2. Sample all disks over a few short intervals to find the busy device.
    $ 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.

  3. Rerun iostat against the busy disk to keep the device view focused on one target.
    $ 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.

  4. Show per-process disk read and write rates during the same interval.
    $ 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.

  5. Filter the process view to a known command when the full task list is noisy.
    $ 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.

  6. Correlate the device and process views before deciding what is slow.

    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.