Skip to main content
RunBook Academy

← All break/fix scenarios in Linux

advancedPerformance~60 min

Break/Fix: the server is slow and every resource looks busy

Reported symptoms

  • Request latency rises across every endpoint, with no error-rate change
  • Interactive shells on the host feel sluggish before any alert fires
  • `free -h` shows memory almost fully used, which the on-call reads as the cause
  • `top` shows CPU at or near 100 per cent, which a second person reads as the cause
  • A lifetime interface counter shows non-zero drops, which a third person reads as the cause

Evidence

  • · `/proc/pressure/cpu` `some avg60` above 10 indicates a genuine run-queue, not just a busy CPU
  • · `grep -E 'MemTotal|MemAvailable' /proc/meminfo` — MemAvailable, not `free`'s used column, is the memory signal
  • · `vmstat 1 5` — non-zero `si`/`so` is the difference between memory pressure and a warm page cache
  • · `iostat -x 1 5` — `await` rising while `r/s`+`w/s` stays flat is the disk-saturation shape
  • · Two samples of `ip -s link show eth0` ten seconds apart — the delta, never the lifetime counter
Diagnosis and resolutionclick to reveal

Root cause

The bottleneck is whichever resource shows saturation, and utilisation on its own never identifies it. A healthy Linux host runs with memory near 100 per cent used because the page cache fills whatever is free and is reclaimable on demand, and a correctly sized batch job runs at 100 per cent CPU by design. In this drill the load is generated deliberately so the saturation signals can be read against a known answer.

Remediation

Identify the saturated resource from its pressure and queueing signals, then drill in with a tool scoped to that resource — pidstat/perf for CPU, vmstat and smaps for memory, iotop/biosnoop for disk, ss and tcpdump for network — before changing anything. The three actions to refuse are adding RAM on the strength of the used column, restarting the service "to free memory", and `echo 3 > /proc/sys/vm/drop_caches`, which discards a warm cache and makes the host measurably slower.

Verification

After the fix, the pressure signal for the identified resource returns to baseline and stays there under the same load, and the resources you ruled out still show the signals that ruled them out. In the drill, Task 7 must leave `pgrep -a stress-ng` empty, `/var/tmp` free space back to its starting value and `/proc/pressure/cpu` back to zero.

Prevention

Record the saturation signals — pressure stall for CPU, memory and IO, plus MemAvailable and interface drop rate — alongside the utilisation graphs, so the on-call has the deciding number to hand instead of the misleading one. Write the triage runbook so each diagnosis line must name the signal and its value, and each rejected resource must name the signal that ruled it out.

A server slow drill: identify the bottleneck resource using the USE methodology, drill in with specific tools, and fix the issue.

Tasks

Task 1: Apply the load

Check headroom first, then generate the load inside a scope with explicit limits:

# Precondition: confirm free space on the scratch filesystem.
df -h /var/tmp
mkdir -p /var/tmp/slowlab

sudo systemd-run --unit=slowlab --scope \
  -p MemoryMax=2G -p CPUQuota=200% \
  stress-ng --timeout 60s \
            --cpu 4 \
            --vm 1 --vm-bytes 1G \
            --hdd 2 --hdd-bytes 256M --temp-path /var/tmp/slowlab

Three details make this safe, and each one is a habit worth carrying into real work:

  • --temp-path pins where the --hdd workers write. Without it they write into the current working directory. Started from /root or / on a small root filesystem, that is a self-inflicted full-filesystem outage.
  • --hdd-bytes bounds how much each worker writes. Without a bound the workers write until the filesystem is full.
  • systemd-run --scope with MemoryMax and CPUQuota keeps the load inside a cgroup, so the kernel throttles the drill rather than OOM-killing something you cared about. It also gives you one handle to stop everything.

Note that --io workers are omitted. They call sync(2) in a tight loop, which flushes the whole page cache repeatedly and distorts every other measurement you are about to take.

The server is now slow. The metrics show the bottleneck.

Task 2: USE methodology

For each resource, check:

  • CPU: utilisation, saturation, errors.

    top
    mpstat 1 5
  • Memory: utilisation, saturation, errors.

    free -h
    vmstat 1 5
  • Disk: utilisation, saturation, errors.

    iostat -x 1 5
  • Network: utilisation, saturation, errors.

    sar -n DEV 1 5        # per-interface throughput (sysstat)
    sar -n EDEV 1 5       # errors and drops - the saturation signal
    ss -s                 # socket totals, including retransmit-heavy states

    nicstat gives a tidier %ifutil column but is not in the Debian, Ubuntu or RHEL base repositories, so it is not available on a host you have just been handed. sar ships with sysstat, which this course installs, and /sys/class/net/<iface>/speed gives the link rate to compute utilisation against.

Task 3: Identify the bottleneck

Based on the USE metrics, identify the resource with the bottleneck. Judge on saturation signals, not on a single utilisation figure:

CPU      %us high AND /proc/pressure/cpu "some avg60" > 10   -> CPU-bound
         (100% CPU with no pressure is a healthy busy job)

Memory   MemAvailable < ~10% of MemTotal AND
         /proc/pressure/memory "some avg60" > 10 AND
         vmstat si/so non-zero                               -> memory-bound
         (high "used" or low "free" on its own means nothing)

Disk     await rising while IOPS is flat, plus tasks in D state
         (thresholds are media-dependent: >20ms is bad for a
          spinning disk, >2ms is bad for NVMe)               -> disk-bound

Network  drop RATE > 0, i.e. the delta of two samples of
         ip -s link or netstat -s - not a lifetime counter   -> network-bound

Collect the signals:

cat /proc/pressure/cpu /proc/pressure/memory /proc/pressure/io
grep -E 'MemTotal|MemAvailable' /proc/meminfo
vmstat 1 5                     # si/so columns
iostat -x 1 5                  # await against r/s + w/s
ip -s link show eth0; sleep 10; ip -s link show eth0

Task 4: Drill in

Use specific tools for the bottleneck:

  • CPU: pidstat, perf top, strace.
  • Memory: pmap, valgrind, slabtop.
  • Disk: iotop, perf record, biosnoop.
  • Network: tcpdump, ss, wireshark.

Task 5: Fix

Apply the fix for the bottleneck:

  • CPU: profile, optimise code, scale out.
  • Memory: identify leak, optimise, add RAM.
  • Disk: faster disk, optimise I/O, add cache.
  • Network: optimise, add bandwidth, fix congestion.

Task 6: Document

Fill in every field yourself. Each diagnosis line must name the signal you read and its value, not just the resource.

TROUBLESHOOTING REPORT: Server Slow
Date:
Symptom:
Impact:
Bottleneck:            (resource + the saturation signal that proves it)
Evidence:              (metric name, value, and where you read it)
Resources ruled out:   (and the signal that ruled each one out)
Root cause:
Fix:
Prevention:

A worked example of the evidence line, from a run of this drill:

Bottleneck:          CPU
Evidence:            /proc/pressure/cpu some avg60=41.7; mpstat %usr 98.2
Resources ruled out: memory - MemAvailable 26 GB of 32 GB, pressure 0.00,
                     vmstat si/so both 0 (page cache was high, which is normal)
                     disk - iostat await 0.4ms, no D-state tasks
                     network - ip -s link drop delta 0 over 10s

Task 7: Clean up

The drill is not finished until the host is back to normal.

sudo systemctl stop slowlab.scope 2>/dev/null
rm -rf /var/tmp/slowlab

# Confirm: no stress-ng left, space returned, pressure back to zero
pgrep -a stress-ng
df -h /var/tmp
cat /proc/pressure/cpu

Knowledge check

Knowledge check · 3 questions

  1. Q1. A user reports the application is slow. free -h shows 61 GB of 64 GB used and 1.2 GB free, but 38 GB buff/cache and 40 GB available. /proc/pressure/memory reads 0.00 and vmstat shows si and so at 0. What is your diagnosis?

  2. Q2. A single reading of 214 dropped packets from ip -s link show eth0 says nothing about whether drops are happening during this incident.

  3. Q3. Why does this drill pass --temp-path and --hdd-bytes to stress-ng instead of running "stress-ng --io 4 --hdd 4"?

Passing score: 75%. Answers are checked in this browser.