Skip to main content
RunBook Academy

← All labs in Linux

Lab · intermediate · ~45 min

Lab: CPU saturation investigation

B · Nested virtualisationC · Simulation

Objectives

  • Detect CPU saturation
  • Compare load average against nproc
  • Distinguish CPU-bound from I/O-bound with isolated load scenarios
  • Identify single-thread bottlenecks and lock contention
  • Recognise stolen CPU on VM-hosted guests
  • Drill down with mpstat and pidstat

Prerequisites

This lab applies USE to CPU on a live host. By the end you will have the discipline for CPU saturation diagnosis.

Tasks

Task 1: Capture baseline

nproc                                 # number of CPUs
uptime                                # load average
mpstat 1 5 > /tmp/baseline-mpstat.txt
mpstat -P ALL 1 5                     # per-CPU, including %steal

Load average only means something next to nproc. A load average of 8 is idle on a 32-core host and a four-times overload on a 2-core one. Write both numbers down now:

echo "nproc=$(nproc) load=$(cut -d' ' -f1-3 /proc/loadavg)"

On a VM, also record %steal from mpstat -P ALL. Steal is time the hypervisor gave to somebody else. A guest at 40% steal is not slow because of anything happening inside the guest, and no amount of in-guest tuning will fix it.

Task 2: Apply stress, one clean signal at a time

Each scenario must produce one signal. A command that mixes CPU workers with I/O workers produces a reading you cannot attribute to either.

# Install stress-ng
sudo apt install stress-ng

# Scenario A - single-thread bottleneck
stress-ng --cpu 1 --timeout 60s

# Scenario B - all CPUs saturated
stress-ng --cpu $(nproc) --timeout 60s

# Scenario C - I/O bound: no CPU workers, real disk I/O, bounded and scoped
mkdir -p /var/tmp/stress-io
stress-ng --hdd 2 --hdd-bytes 512M --temp-path /var/tmp/stress-io --timeout 60s
rm -rf /var/tmp/stress-io          # cleanup

# Scenario D - lock contention
stress-ng --futex 4 --timeout 60s

Task 3: Observe during stress (in another terminal)

mpstat -P ALL 1 5
# Look for: %us, %wa, %sys, %steal, per-CPU spread

pidstat 1 5
# Look for: which process, %CPU, threads

pidstat -w 1 5
# Look for: cswch/s (voluntary) vs nvcswch/s (involuntary)

vmstat 1 5
# Look for: r (run queue), b (blocked), cs (context switches)

iostat -x 1 5
# Look for: await, %util on the device under test

Task 4: Document the patterns

Scenario A - single-thread stress:
- mpstat -P ALL: 1 CPU at ~100% %us, others idle
- pidstat: stress-ng-cpu at ~100% of one CPU
- vmstat: r=1, b=0, no wait

Scenario B - all-CPU stress:
- mpstat -P ALL: every CPU at ~100% %us
- pidstat: stress-ng-cpu processes spread across CPUs
- vmstat: r >= NCPUs, b=0, no wait

Scenario C - disk I/O stress (--hdd):
- mpstat -P ALL: %wa elevated, %us low (CPUs idle waiting on disk)
- pidstat: stress-ng-hdd processes, modest %CPU
- vmstat: b (blocked) > 0
- iostat -x: await elevated, %util near 100 on the target device

Scenario D - lock contention (--futex):
- mpstat -P ALL: %sys elevated, %us low
- pidstat -w: cswch/s very high (voluntary - threads blocking on the lock)
- vmstat: cs (context switches) very high, r low relative to the
  number of runnable threads

Record the actual numbers you measured next to each line. Where your reading disagrees with the expected pattern, that disagreement is the finding — investigate it rather than overwriting it.

Task 5: Recognise each pattern

For each stress scenario, identify:

  • CPU bound: high %us, low %wa, r > CPUs. (Scenario B)
  • I/O bound: high %wa, low %us, b > 0, iostat await elevated. (Scenario C)
  • Single-thread bottleneck: 1 CPU at 100%, others idle, load average pinned near 1 regardless of nproc. (Scenario A)
  • Multi-thread efficient: CPUs all near saturation, balanced. (Scenario B)
  • Lock contention: high cswch/s, high %sys, low %us. (Scenario D)
  • Stolen CPU: %steal high, %us and %wa both low, and the guest feels slow with no in-guest cause. Not reproducible from inside the guest — check it on every VM-hosted investigation.

The CPU-bound versus I/O-bound branch is the most consequential decision in a real slow-server incident, because the two lead to completely different remediations: more cores or a faster algorithm on one side, faster storage or fewer synchronous writes on the other. Scenarios B and C are the two halves of that branch, which is why they must be run separately.

Deliverables

  • · USE checklist for CPU
  • · Drill-down output
  • · Diagnosis report

Verification status

Last reviewed
2026-08-09
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.