CPU Monitoring on Linux

CPU Monitoring on Linux

CPU monitoring on Linux is less about a single “CPU usage” number and more about understanding what kind of work the CPU is doing, whether that work is spread evenly across cores, and whether the CPU is actually the bottleneck at all. A system can look CPU-bound in a glance and turn out to be waiting on disk the whole time.

The Quick Check: top

top

The header shows an aggregate CPU line:

%Cpu(s): 12.3 us,  4.1 sy,  0.0 ni, 82.9 id,  0.5 wa,  0.0 hi,  0.2 si,  0.0 st
FieldMeaning
usUser-space application code
syKernel (system) code run on behalf of processes
niUser-space processes running at a adjusted “nice” priority
idIdle
waI/O wait: CPU idle while waiting on disk/network with runnable work available
hiHardware interrupt handling
siSoftware interrupt handling
stTime stolen by a hypervisor, on virtualized systems

By default top shows an aggregate across all cores. Press 1 to expand into a per-core breakdown, which matters because averages can hide a single saturated core behind many idle ones.

A More Visual Alternative: htop

sudo apt install htop   # or dnf install htop
htop

htop shows per-core usage as color-coded bars at the top of the screen by default, alongside a sortable, scrollable process list. It’s generally easier to read at a glance than top, though top has the advantage of being preinstalled on nearly every Linux system, which matters when working on unfamiliar or minimal servers.

Per-Core Detail: mpstat

mpstat -P ALL 1
CPU    %usr   %nice    %sys %iowait    %irq   %soft  %idle
all    12.30    0.00    4.10    0.50    0.00    0.20  82.90
  0    45.00    0.00    8.20    0.10    0.00    0.10  46.60
  1     3.10    0.00    1.90    0.80    0.00    0.30  93.90
  2     8.40    0.00    3.50    0.60    0.00    0.20  87.30
  3     2.90    0.00    2.60    0.40    0.00    0.10  94.00

mpstat, part of the sysstat package, prints plain-text lines rather than redrawing a screen, making it well suited to logging, scripting, or SSH sessions with limited bandwidth. The all line is the aggregate; each numbered line is one core. In this example, core 0 is clearly doing more work than the others, which an aggregate-only view would have hidden.

Why Per-Core View Matters

A workload that’s single-threaded can only ever use one core, no matter how many are available. On an 8-core machine, a single-threaded process pegging one core at 100% only moves the aggregate CPU usage to about 12.5%, which looks unremarkable in an aggregate summary but means that specific workload is completely CPU-bound on the one core it can use. Checking per-core data (via mpstat -P ALL or top with per-core view enabled) is the only way to catch this.

Finding the Process Responsible

ps aux --sort=-%cpu | head -n 11

or interactively, top and htop both sort by CPU usage by default (press Shift+P in top to force this sort order if it’s changed).

Distinguishing CPU-Bound from I/O-Bound

A system that “feels slow” isn’t necessarily CPU-bound. The wa (I/O wait) field is the key differentiator:

  • High us/sy, low wa: genuinely CPU-bound, the CPU is busy doing real work
  • Low us/sy, high wa: I/O-bound, the CPU is idle waiting on disk or network, not actually the bottleneck
vmstat 1

A high b column (processes blocked on I/O) alongside high wa confirms an I/O bottleneck. From there, iostat -xz 1 identifies which specific device is saturated, and iotop (requires root) identifies which process is generating the I/O load.

A Practical Triage Order

  1. top (press 1 for per-core view), get the overall picture and check wa
  2. If us/sy dominate: ps aux --sort=-%cpu to find the responsible process
  3. If wa dominates: pivot to iostat and iotop instead of chasing CPU
  4. mpstat -P ALL 1 when you need per-core detail or a loggable, scriptable stream

Frequently Asked Questions

What is the difference between us, sy, and wa in CPU monitoring output?

us is the percentage of CPU time spent running user-space application code, sy is time spent running kernel code on behalf of a process (such as system calls), and wa is time the CPU was idle while waiting for an I/O operation to complete despite having runnable work. High us points to application workload, high sy points to heavy kernel activity such as frequent system calls, and high wa points to a storage or I/O bottleneck rather than a CPU problem.

How do I check CPU usage per core instead of an aggregate average?

Run mpstat -P ALL 1 to see a separate line for each CPU core every second, or open top and press 1 to toggle per-core display instead of the aggregate. Per-core view matters because a workload can show low overall CPU usage while one specific core is fully saturated, which is common with single-threaded applications that cannot use more than one core no matter how many are available.

What does high sy (system) time usually indicate?

High sy time means the kernel is spending significant CPU time on behalf of processes, commonly from frequent system calls, context switching, interrupt handling, or network stack processing. Sustained high sy alongside high network or disk throughput is often expected. Sustained high sy without a clear matching workload can indicate excessive small system calls, a misbehaving driver, or contention somewhere in the kernel worth investigating with tools like strace or perf.

How can a system show low CPU usage but still feel slow?

This typically happens when the bottleneck is I/O rather than CPU. Processes waiting on disk or network operations show up as high wa (I/O wait) rather than us or sy, and the CPU itself may be mostly idle while work is stalled. Checking vmstat 1 for a high b (blocked processes) column alongside high wa, then following up with iostat, usually confirms an I/O bottleneck rather than a CPU one.

What is the difference between top, htop, and mpstat for CPU monitoring?

top is available on virtually every Linux system by default and shows an aggregate or per-core CPU breakdown alongside per-process usage in a single screen. htop is a more visual, interactive alternative with color-coded per-core bars and easier sorting and process management, but requires separate installation on some distributions. mpstat, part of the sysstat package, is more scriptable and better suited to logging or long-running sampling since it prints plain text lines rather than redrawing a full screen.

How do I find which specific process is using the most CPU?

Run top or htop and processes are sorted by CPU usage by default (press Shift+P in top to force this sort if it has changed). For a one-off snapshot without an interactive tool, ps aux —sort=-%cpu | head -n 11 lists the ten processes using the most CPU at the moment the command runs.