Performance Monitoring Tools
Finding what is using CPU, memory, and disk with top/htop, df/du, and iostat.
The first question: which resource is actually the problem
"The server is slow" isn't yet a diagnosable problem — it could be CPU, memory, disk I/O, or occasionally network, and the fix for each is completely different. The tools on this page each answer one of those questions specifically, and the practical skill is knowing which one to reach for first rather than staring at all of them at once.
CPU and memory at a glance: top and htop
top is available on essentially every Linux system with no installation required, and gives a live, auto-refreshing snapshot of what's consuming CPU and memory right now:
$ top
top - 09:41:22 up 12 days, 3:20, 2 users, load average: 2.15, 1.87, 1.42
Tasks: 148 total, 2 running, 146 sleeping, 0 stopped, 0 zombie
%Cpu(s): 42.3 us, 8.1 sy, 0.0 ni, 47.2 id, 1.9 wa, 0.0 hi, 0.5 si, 0.0 st
MiB Mem : 7938.4 total, 412.1 free, 5203.8 used, 2322.5 buff/cache
MiB Swap: 2048.0 total, 1890.2 free, 157.8 used
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
18420 deploy 20 0 1204812 84212 22140 S 38.2 1.0 4:12.88 node
1204 mysql 20 0 2104512 512340 18220 S 22.5 6.3 61:20.14 mysqld
The header lines matter as much as the process table:
load average— three numbers (1, 5, and 15-minute averages) roughly representing how many processes were runnable (running or waiting for CPU) on average. A load average of2.15on a 4-core machine means the CPUs have real spare capacity; the same2.15on a 1-core machine means the system is oversubscribed and processes are queuing for CPU time.%Cpu(s)—us(user-space time),sy(kernel/system time),wa(time waiting on I/O — highwausually points at disk, not CPU, as the real bottleneck), and a few less common categories.buff/cache— memory the kernel is using for disk caching. This is not "wasted" memory — it's automatically reclaimed the instant an application needs it — so a system reporting little "free" memory but a largebuff/cachefigure is usually completely healthy, not low on memory.
Sorting is the single most useful top interaction: press Shift+P to sort by CPU (the default), Shift+M to sort by memory, letting the worst offender surface to the top of the list immediately.
htop is a community-built, friendlier alternative — color-coded per-core CPU bars, a scrollable/searchable process tree, and mouse support — functionally the same data as top, just easier to read at a glance:
$ sudo apt install htop # not preinstalled on most systems, unlike top
$ htop
Disk space: df and du
df (disk free) reports space at the filesystem level — how full each mounted partition is:
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 38G 9.5G 81% /
/dev/sdb1 200G 145G 45G 76% /var/www
tmpfs 3.9G 0 3.9G 0% /dev/shm
-h (human-readable) shows sizes in GB/MB instead of raw byte counts — always worth adding, since the default output is nearly unreadable. A filesystem near 100% is worth investigating immediately: many services (databases especially) fail ungracefully, not gracefully, when they can't write to a full disk.
du (disk usage) answers a different question — not "how full is this filesystem" but "which specific directory is actually using all that space":
# Total size of everything under a directory, human-readable
$ du -sh /var/log
2.3G /var/log
# One level of subdirectories, sorted largest first
$ du -h --max-depth=1 /var/log | sort -rh
2.3G /var/log
1.8G /var/log/nginx
420M /var/log/mysql
90M /var/log/journal
The typical workflow: df -h tells you that / is nearly full, then du -h --max-depth=1 / (repeated, descending into whichever subdirectory is largest each time) narrows down which directory is actually responsible — usually an unrotated log file or an old backup nobody cleaned up.
Disk I/O: iostat
CPU and memory can look completely healthy while a system is still slow because it's waiting on disk — the wa figure in top's header is the first hint, and iostat (from the sysstat package) gives the detail behind it:
$ sudo apt install sysstat
$ iostat -xz 2
Linux 6.8.0-45-generic 08/26/2026 _x86_64_ (4 CPU)
Device r/s w/s rkB/s wkB/s await %util
sda 2.10 45.80 84.2 3204.6 18.42 92.15
await— average time (in milliseconds) a request waits, including queuing plus actual service time. Risingawaitunder load is the clearest sign disk latency is the bottleneck.%util— the percentage of time the device was busy servicing requests; sustained values near 100% mean the disk itself is saturated and is very likely the actual constraint, not CPU or application code.
iostat -xz 2 repeats every 2 seconds (-x for extended stats, -z to skip devices with zero activity), which matters because the very first sample iostat prints is a since-boot average, not a live reading — always look at the second and later samples for a real current picture.
Putting it together: a decision path
| Symptom | Check first | What it tells you |
|---|---|---|
| "The server feels slow" overall | top/htop — look at load average and %Cpu(s) |
Whether CPU is actually saturated, and by which process |
High wa in top's CPU line |
iostat -xz 2 — look at await and %util |
Whether disk I/O, not CPU, is the real bottleneck |
| "Disk full" errors from an app | df -h, then du -h --max-depth=1 narrowing down |
Which filesystem is full, and which directory is responsible |
| One process using way more memory than expected | top/htop sorted by %MEM (Shift+M) |
Which specific process to investigate or restart |
| Suspect swapping is hurting performance | top's Swap: line, or vmstat 2 |
Whether the system is actively swapping (a serious performance cliff) versus just having some swap allocated |
Common mistakes
- Reading
freememory as "how much memory is actually available" — thebuff/cachefigure is reclaimable on demand, so a low "free" number with a large "buff/cache" number is normal and healthy, not a sign of a memory problem. - Treating the very first
iostatsample as current data — it's a since-boot average; the meaningful numbers are the second sample onward. - Chasing a CPU-bound investigation (
top, process-level tuning) whenwais actually high and disk I/O is the real constraint — checkiostatbefore assuming the bottleneck is CPU. - Running
du -sh /on a large, busy production filesystem without--max-depth— a full recursive scan of an entire disk can take a long time and itself adds I/O load to a system that may already be struggling. - Not installing
sysstat/htopahead of time — reaching foriostatorhtopfor the first time in the middle of an actual incident, only to discover it isn't installed and now needs a package manager (and internet access) that may itself be affected by whatever's going wrong.