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:

Bash
$ 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 of 2.15 on a 4-core machine means the CPUs have real spare capacity; the same 2.15 on 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 — high wa usually 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 large buff/cache figure 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:

Bash
$ 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:

Bash
$ 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":

Bash
# 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:

Bash
$ 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. Rising await under 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 free memory as "how much memory is actually available" — the buff/cache figure 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 iostat sample 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) when wa is actually high and disk I/O is the real constraint — check iostat before 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/htop ahead of time — reaching for iostat or htop for 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.