Intensive disk activity on Linux often explains sluggish shells, slow applications, or stalled services even when CPU graphs look normal. Observing disk Input/Output (I/O) in detail highlights which workloads saturate storage bandwidth or create long queues, making performance investigations more accurate than relying on CPU metrics alone.
The kernel maintains counters for block devices and processes, and tools such as iostat, iotop, and ioping convert those counters into readable statistics. iostat focuses on per-disk throughput and utilization, iotop attributes disk I/O to individual processes or threads in real time, and ioping probes latency by repeatedly issuing small read operations against a device or directory.
Choosing sensible sampling intervals and scopes keeps monitoring overhead low while still surfacing patterns such as sustained high iowait or sudden spikes in response times. Detailed per-process and per-device statistics typically require a user with sudo privileges, and running aggressive latency tests on production storage can add extra load if intervals are too short.
Related: How to check disk errors in Linux
Related: How to mount disks and partitions in Linux
Steps to monitor disk activity on Linux:
- Open a terminal on the Linux system using a user account that can run sudo.
- Install the sysstat package that provides the iostat command on Ubuntu and other Debian-based systems.
$ sudo apt update Hit:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease Hit:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease Hit:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease Reading package lists... Building dependency tree... Reading state information... 5 packages can be upgraded. Run 'apt list --upgradable' to see them. $ sudo apt install --assume-yes sysstat Reading package lists... Building dependency tree... Reading state information... The following additional packages will be installed: libsensors-config libsensors5 xz-utils Suggested packages: lm-sensors isag The following NEW packages will be installed: libsensors-config libsensors5 sysstat xz-utils 0 upgraded, 4 newly installed, 0 to remove and 5 not upgraded. Need to get 781 kB of archives. After this operation, 2597 kB of additional disk space will be used. Get:1 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 libsensors-config all 1:3.6.0-9build1 [5546 B] Get:2 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 libsensors5 arm64 1:3.6.0-9build1 [27.0 kB] Get:3 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 xz-utils arm64 5.6.1+really5.4.5-1ubuntu0.2 [268 kB] Get:4 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 sysstat arm64 12.6.1-2 [480 kB] Fetched 781 kB in 3s (309 kB/s) Selecting previously unselected package libsensors-config. (Reading database ... (Reading database ... 5%(Reading database ... 10%(Reading database ... 15%(Reading database ... 20%(Reading database ... 25%(Reading database ... 30%(Reading database ... 35%(Reading database ... 40%(Reading database ... 45%(Reading database ... 50%(Reading database ... 55%(Reading database ... 60%(Reading database ... 65%(Reading database ... 70%(Reading database ... 75%(Reading database ... 80%(Reading database ... 85%(Reading database ... 90%(Reading database ... 95%(Reading database ... 100%(Reading database ... 17940 files and directories currently installed.) Preparing to unpack .../libsensors-config_1%3a3.6.0-9build1_all.deb ... Unpacking libsensors-config (1:3.6.0-9build1) ... Selecting previously unselected package libsensors5:arm64. Preparing to unpack .../libsensors5_1%3a3.6.0-9build1_arm64.deb ... Unpacking libsensors5:arm64 (1:3.6.0-9build1) ... Selecting previously unselected package xz-utils. Preparing to unpack .../xz-utils_5.6.1+really5.4.5-1ubuntu0.2_arm64.deb ... Unpacking xz-utils (5.6.1+really5.4.5-1ubuntu0.2) ... Selecting previously unselected package sysstat. Preparing to unpack .../sysstat_12.6.1-2_arm64.deb ... Unpacking sysstat (12.6.1-2) ... Setting up libsensors-config (1:3.6.0-9build1) ... Setting up xz-utils (5.6.1+really5.4.5-1ubuntu0.2) ... update-alternatives: using /usr/bin/xz to provide /usr/bin/lzma (lzma) in auto mode update-alternatives: warning: skip creation of /usr/share/man/man1/lzma.1.gz because associated file /usr/share/man/man1/xz.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/unlzma.1.gz because associated file /usr/share/man/man1/unxz.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzcat.1.gz because associated file /usr/share/man/man1/xzcat.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzmore.1.gz because associated file /usr/share/man/man1/xzmore.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzless.1.gz because associated file /usr/share/man/man1/xzless.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzdiff.1.gz because associated file /usr/share/man/man1/xzdiff.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzcmp.1.gz because associated file /usr/share/man/man1/xzcmp.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzgrep.1.gz because associated file /usr/share/man/man1/xzgrep.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzegrep.1.gz because associated file /usr/share/man/man1/xzegrep.1.gz (of link group lzma) doesn't exist update-alternatives: warning: skip creation of /usr/share/man/man1/lzfgrep.1.gz because associated file /usr/share/man/man1/xzfgrep.1.gz (of link group lzma) doesn't exist Setting up libsensors5:arm64 (1:3.6.0-9build1) ... Setting up sysstat (12.6.1-2) ... debconf: unable to initialize frontend: Dialog debconf: (Dialog frontend will not work on a dumb terminal, an emacs shell buffer, or without a controlling terminal.) debconf: falling back to frontend: Readline debconf: unable to initialize frontend: Readline debconf: (Can't locate Term/ReadLine.pm in @INC (you may need to install the Term::ReadLine module) (@INC entries checked: /etc/perl /usr/local/lib/aarch64-linux-gnu/perl/5.38.2 /usr/local/share/perl/5.38.2 /usr/lib/aarch64-linux-gnu/perl5/5.38 /usr/share/perl5 /usr/lib/aarch64-linux-gnu/perl-base /usr/lib/aarch64-linux-gnu/perl/5.38 /usr/share/perl/5.38 /usr/local/lib/site_perl) at /usr/share/perl5/Debconf/FrontEnd/Readline.pm line 8.) debconf: falling back to frontend: Teletype Creating config file /etc/default/sysstat with new version update-alternatives: using /usr/bin/sar.sysstat to provide /usr/bin/sar (sar) in auto mode update-alternatives: warning: skip creation of /usr/share/man/man1/sar.1.gz because associated file /usr/share/man/man1/sar.sysstat.1.gz (of link group sar) doesn't exist Created symlink /etc/systemd/system/sysstat.service.wants/sysstat-collect.timer -> /usr/lib/systemd/system/sysstat-collect.timer. Created symlink /etc/systemd/system/sysstat.service.wants/sysstat-summary.timer -> /usr/lib/systemd/system/sysstat-summary.timer. Created symlink /etc/systemd/system/multi-user.target.wants/sysstat.service -> /usr/lib/systemd/system/sysstat.service. Processing triggers for libc-bin (2.39-0ubuntu8.6) ...
On RHEL, CentOS, and Fedora, install using sudo dnf install --assumeyes sysstat.
- Run iostat without options to display CPU usage and block device statistics since boot.
$ iostat Linux 6.12.54-linuxkit (host.example.net) 01/13/26 _aarch64_ (10 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 0.51 0.00 0.24 0.00 0.00 99.24 Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd loop0 0.00 0.05 1.23 4.71 7875 205988 786444 vda 1.58 3.08 254.27 12062.34 515061 42462540 2014358104 vdb 0.43 418.13 0.00 0.00 69825840 0 0The avg-cpu section summarizes CPU time breakdown, and the Device table lists per-device throughput and request rates.
- Focus on a specific disk by passing the device path to iostat together with an interval in seconds for continuous monitoring.
$ iostat /dev/vdb -y 1 3 Linux 6.12.54-linuxkit (host.example.net) 01/13/26 _aarch64_ (10 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 0.00 0.00 0.00 0.00 0.00 100.00 Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd vdb 0.00 0.00 0.00 0.00 0 0 0 avg-cpu: %user %nice %system %iowait %steal %idle 0.00 0.00 0.10 0.00 0.00 99.90 Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd vdb 0.00 0.00 0.00 0.00 0 0 0 avg-cpu: %user %nice %system %iowait %steal %idle 0.00 0.00 0.00 0.00 0.00 100.00 Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd vdb 0.00 0.00 0.00 0.00 0 0 0Higher tps and write rates on a device indicate more active workloads using that disk.
- Install iotop to monitor per-process disk I/O usage on Ubuntu and other Debian-based systems.
$ sudo apt update Hit:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease Hit:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease Hit:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease Reading package lists... Building dependency tree... Reading state information... 5 packages can be upgraded. Run 'apt list --upgradable' to see them. $ sudo apt install --assume-yes iotop Reading package lists... Building dependency tree... Reading state information... The following NEW packages will be installed: iotop 0 upgraded, 1 newly installed, 0 to remove and 5 not upgraded. Need to get 24.4 kB of archives. After this operation, 163 kB of additional disk space will be used. Get:1 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 iotop arm64 0.6-42-ga14256a-0.2build1 [24.4 kB] Fetched 24.4 kB in 1s (26.6 kB/s) Selecting previously unselected package iotop. (Reading database ... (Reading database ... 5%(Reading database ... 10%(Reading database ... 15%(Reading database ... 20%(Reading database ... 25%(Reading database ... 30%(Reading database ... 35%(Reading database ... 40%(Reading database ... 45%(Reading database ... 50%(Reading database ... 55%(Reading database ... 60%(Reading database ... 65%(Reading database ... 70%(Reading database ... 75%(Reading database ... 80%(Reading database ... 85%(Reading database ... 90%(Reading database ... 95%(Reading database ... 100%(Reading database ... 18160 files and directories currently installed.) Preparing to unpack .../iotop_0.6-42-ga14256a-0.2build1_arm64.deb ... Unpacking iotop (0.6-42-ga14256a-0.2build1) ... Setting up iotop (0.6-42-ga14256a-0.2build1) ... update-alternatives: using /usr/sbin/iotop-py to provide /usr/sbin/iotop (iotop) in auto mode update-alternatives: warning: skip creation of /usr/share/man/man8/iotop.8.gz because associated file /usr/share/man/man8/iotop-py.8.gz (of link group iotop) doesn't exist
On RHEL, CentOS, and Fedora, install using sudo dnf install --assumeyes iotop.
- Run iotop with sudo to see real-time disk read and write activity for all running processes.
$ sudo iotop -b -n 1 Total DISK READ: 0.00 B/s | Total DISK WRITE: 0.00 B/s Current DISK READ: 0.00 B/s | Current DISK WRITE: 0.00 B/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO COMMAND 1 be/4 root 0.00 B/s 0.00 B/s ?unavailable? systemd 23 be/3 root 0.00 B/s 0.00 B/s ?unavailable? systemd-journald 171 be/4 messageb 0.00 B/s 0.00 B/s ?unavailable? @dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only 174 be/4 root 0.00 B/s 0.00 B/s ?unavailable? systemd-logind 1426 be/4 systemd- 0.00 B/s 0.00 B/s ?unavailable? systemd-timesyncd 1427 be/4 systemd- 0.00 B/s 0.00 B/s ?unavailable? systemd-timesyncd [sd-resolve] 3365 be/4 syslog 0.00 B/s 0.00 B/s ?unavailable? rsyslogd -n -iNONE 3367 be/4 syslog 0.00 B/s 0.00 B/s ?unavailable? rsyslogd -n -iNONE [in:imuxsock] 3368 be/4 syslog 0.00 B/s 0.00 B/s ?unavailable? rsyslogd -n -iNONE [in:imklog] 3369 be/4 syslog 0.00 B/s 0.00 B/s ?unavailable? rsyslogd -n -iNONE [rs:main Q:Reg] 3846 be/4 root 0.00 B/s 0.00 B/s ?unavailable? sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups 7319 be/4 root 0.00 B/s 0.00 B/s ?unavailable? cron -f -P 10215 be/4 root 0.00 B/s 0.00 B/s ?unavailable? sudo iotop -b -n 1 10221 be/4 root 0.00 B/s 0.00 B/s ?unavailable? python3 /usr/sbin/iotop -b -n 1The DISK READ and DISK WRITE columns show per-process throughput, and the IO column reflects time spent waiting on disk operations when the kernel exposes task statistics.
- Filter the view to only processes currently performing disk I/O by using the --only option with iotop.
$ sudo iotop -b -n 1 --only Total DISK READ: 0.00 B/s | Total DISK WRITE: 0.00 B/s Current DISK READ: 0.00 B/s | Current DISK WRITE: 0.00 B/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO COMMAND
Omit --only to display idle processes as well when the filtered view is empty.
- Install ioping to measure disk latency and responsiveness on Ubuntu and other Debian-based systems.
$ sudo apt update Hit:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease Hit:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease Hit:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease Reading package lists... Building dependency tree... Reading state information... 5 packages can be upgraded. Run 'apt list --upgradable' to see them. $ sudo apt install --assume-yes ioping Reading package lists... Building dependency tree... Reading state information... The following NEW packages will be installed: ioping 0 upgraded, 1 newly installed, 0 to remove and 5 not upgraded. Need to get 17.8 kB of archives. After this operation, 86.0 kB of additional disk space will be used. Get:1 http://ports.ubuntu.com/ubuntu-ports noble/universe arm64 ioping arm64 1.3-1 [17.8 kB] Fetched 17.8 kB in 1s (20.5 kB/s) Selecting previously unselected package ioping. (Reading database ... (Reading database ... 5%(Reading database ... 10%(Reading database ... 15%(Reading database ... 20%(Reading database ... 25%(Reading database ... 30%(Reading database ... 35%(Reading database ... 40%(Reading database ... 45%(Reading database ... 50%(Reading database ... 55%(Reading database ... 60%(Reading database ... 65%(Reading database ... 70%(Reading database ... 75%(Reading database ... 80%(Reading database ... 85%(Reading database ... 90%(Reading database ... 95%(Reading database ... 100%(Reading database ... 18181 files and directories currently installed.) Preparing to unpack .../ioping_1.3-1_arm64.deb ... Unpacking ioping (1.3-1) ... Setting up ioping (1.3-1) ...
On RHEL, CentOS, and Fedora, install using sudo dnf install --assumeyes ioping.
- Measure latency for a block device or directory by running ioping against the desired target.
$ sudo ioping -c 6 /dev/vdb 4 KiB <<< /dev/vdb (block device 606.5 MiB): request=1 time=119.4 us (warmup) 4 KiB <<< /dev/vdb (block device 606.5 MiB): request=2 time=251.9 us 4 KiB <<< /dev/vdb (block device 606.5 MiB): request=3 time=639.8 us 4 KiB <<< /dev/vdb (block device 606.5 MiB): request=4 time=142.5 us 4 KiB <<< /dev/vdb (block device 606.5 MiB): request=5 time=596.0 us 4 KiB <<< /dev/vdb (block device 606.5 MiB): request=6 time=305.9 us --- /dev/vdb (block device 606.5 MiB) ioping statistics --- 5 requests completed in 1.94 ms, 20 KiB read, 2.58 k iops, 10.1 MiB/s generated 6 requests in 5.01 s, 24 KiB, 1 iops, 4.79 KiB/s min/avg/max/mdev = 142.5 us / 387.2 us / 639.8 us / 196.1 us
High-frequency latency tests on busy or fragile storage can increase load and may stress underlying disks if run for long periods.
- Verify monitoring by performing a disk-heavy operation, such as copying a large file, and confirming that throughput and latency values increase in iostat, iotop, and ioping while the operation is in progress.
Use simple commands such as cp largefile.img /tmp/ in another terminal to generate test disk activity when validating the monitoring setup.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
