"The server is slow" is the least specific bug report in operations, and the first few minutes after it decide whether you find the cause or spend an hour staring at the wrong graph. There are dozens of Linux performance tools. You only need a handful, run in a sensible order, to tell the four main bottlenecks apart: CPU, memory, disk and network.
This is the routine I use on call. It takes about ten minutes, needs only packages present on most
distributions (procps, sysstat, iproute2), and every step narrows the next one.
Minute 0-1: what does "slow" mean, and since when?
Before touching the server, get two facts: which operation is slow (every request, one endpoint, a batch job) and when it started. A deploy, a cron job or a traffic change at that time is often the answer. Then log in and get context:
uptime
03:12:40 up 41 days, 2:01, 1 user, load average: 18.42, 15.10, 6.33
Load average counts tasks that are runnable or in uninterruptible sleep (usually waiting for disk).
On Linux a high load can mean CPU saturation or I/O waiting; it does not say which. Compare with the
number of CPUs (nproc) and look at the trend: 18 now, 6 fifteen minutes ago means something recent.
dmesg -T | tail -20
Kernel messages reveal OOM kills, disk errors, network link flaps and hung tasks in seconds. Skipping this step is the most common reason people miss the real cause.
Minute 1-2: pressure stall information
On kernels 4.20 and newer, PSI tells you directly how much time tasks spend stalled waiting for each resource:
cat /proc/pressure/cpu /proc/pressure/memory /proc/pressure/io
some avg10=12.40 avg60=10.98 avg300=4.20 total=...
some avg10=0.00 avg60=0.00 avg300=0.00 total=...
full avg10=0.00 avg60=0.00 avg300=0.00 total=...
some avg10=61.20 avg60=55.10 avg300=20.71 total=...
full avg10=48.90 avg60=44.30 avg300=16.02 total=...
someis the share of time at least one task was stalled on that resource.fullis the share of time all non-idle tasks were stalled at once, meaning no useful work.
Here I/O pressure is very high (full 48.9% over the last 10 seconds) while memory is zero. That points
straight at storage. PSI is the fastest single signal for which resource to investigate.
Minute 2-4: vmstat, the system overview
vmstat -w 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- --------cpu--------
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 14 0 812340 10204 9123400 0 0 18204 92110 4120 6230 6 4 22 68 0
3 15 0 809112 10204 9124520 0 0 17988 95004 4210 6410 5 4 20 71 0
The columns that matter:
| Column | Meaning | Worry when |
|---|---|---|
r |
Runnable tasks | Consistently above CPU count: CPU saturation |
b |
Tasks blocked in uninterruptible sleep | Non-zero and climbing: usually I/O |
si, so |
Swap in/out per second | Any sustained non-zero: memory pressure |
bi, bo |
Blocks read/written per second | High with high wa: disk bound |
us, sy |
User and kernel CPU | us+sy near 100: CPU bound |
wa |
CPU idle waiting for I/O | High: storage is the bottleneck |
st |
Time stolen by the hypervisor | Non-zero on VMs: noisy neighbour or exhausted burst credits |
Ignore the first line; it shows averages since boot. In this output, b is 14, wa near 70% and bo
high: processes are waiting for writes.
Quick reading guide:
- High
r, highus, lowwa: CPU. Go topidstat -uandtop. - Non-zero
si/so, lowfreeand smallcache: memory. Go tofree -mand per-process RSS. - High
b, highwa: disk. Go toiostat. - Everything low but requests are slow: look outside the box, at network or dependencies.
Minute 4-6: iostat, which device and how bad
iostat -xz 1 3
Device r/s w/s rkB/s wkB/s r_await w_await aqu-sz %util
nvme1n1 12.0 1840.0 480.0 94210.0 1.20 38.90 71.40 100.00
nvme0n1 3.0 14.0 40.0 210.0 0.40 0.80 0.02 1.20
r_await,w_await: average time in milliseconds a request spent queued plus being served. For NVMe, single-digit values are normal; 39 ms of write latency is not.aqu-sz: average queue length. Large values mean requests are piling up.%util: share of time the device was busy. For modern SSDs that handle many requests in parallel, 100% does not always mean saturated; trustawaitandaqu-szmore.
The data volume nvme1n1 is overloaded with writes. On cloud volumes, also check whether you have hit
the provisioned IOPS or throughput limit, which looks exactly like this.
Minute 6-8: pidstat, which process
Now find who is doing it:
pidstat -d 1 5 # disk I/O per process
pidstat -u 1 5 # CPU per process
pidstat -r 1 5 # memory faults and RSS per process
03:14:02 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
03:14:03 PM 999 4122 0.00 88104.00 0.00 212 postgres
03:14:03 PM 0 9310 0.00 5120.00 0.00 18 rsync
PostgreSQL is writing 88 MB/s. That turns "the server is slow" into a specific question for the
database: checkpoint, vacuum, a bulk import or a query spilling to disk? pidstat -d is often the step
that ends the investigation.
For threads within a process, add -t. For a single process over time, -p PID.
Minute 8-9: memory, if vmstat pointed there
free -m
total used free shared buff/cache available
Mem: 31890 29812 410 88 1668 1402
Swap: 4095 3890 205
Look at available, not free. Linux uses spare memory for cache, so low free is normal. Low
available plus swap activity in vmstat means real pressure. Then check which processes hold memory and
whether the OOM killer has fired:
ps -eo pid,rss,comm --sort=-rss | head
journalctl -k | grep -i 'out of memory'
Minute 9-10: network and dependencies
If the box looks healthy, the slowness is often in what it talks to:
ss -s # socket summary: established, time-wait, orphaned
ss -tin state established '( dport = :5432 )' | head # rtt and retransmits to the DB
sar -n DEV 1 3 # interface throughput
sar -n EDEV 1 3 # interface errors and drops
Rising retransmits, a pile of connections in SYN-SENT or CLOSE-WAIT, or a saturated interface each
tell a different story: network loss, an unreachable dependency, an application not closing sockets, or
bandwidth exhaustion.
Common mistakes
| Mistake | Why it hurts | Do this instead |
|---|---|---|
| Reading load average as CPU usage | On Linux it includes I/O wait | Check vmstat r vs b and PSI |
| Trusting the first line of vmstat or iostat | It is the since-boot average | Read from the second sample |
| Panicking about low "free" memory | Cache is reclaimable | Use the available column |
| Treating 100% util on SSD as saturation | SSDs work in parallel | Look at await and queue size |
Skipping dmesg |
Misses OOM kills, disk errors | Always read recent kernel messages |
| Running heavy tools on a dying box | Makes it worse | Start with cheap commands, sample briefly |
Make it repeatable
Turn the routine into a script that collects everything in one go, so the next person on call gets the same evidence:
#!/usr/bin/env bash
set -u
out="/tmp/triage-$(hostname)-$(date +%Y%m%dT%H%M%S).txt"
{
uptime; nproc; dmesg -T | tail -30
cat /proc/pressure/{cpu,memory,io}
vmstat -w 1 5; iostat -xz 1 3; free -m
pidstat -u -d -r 1 3
ss -s; sar -n DEV 1 3
} > "$out" 2>&1
echo "saved $out"
Keep sysstat collection enabled (sadc via its timer) so you can look at the past with sar when the
problem has already stopped.
Key takeaways
- Ask what is slow and since when, then read
uptimeanddmesgfirst. - PSI shows in seconds which resource tasks are stalled on.
vmstattells CPU, memory and I/O apart; ignore its first line.iostat -xlatency and queue size matter more than%utilon SSDs.pidstatnames the process, which turns a vague complaint into a specific question.
References
- Linux kernel documentation, PSI: https://docs.kernel.org/accounting/psi.html
- vmstat manual: https://man7.org/linux/man-pages/man8/vmstat.8.html
- iostat manual: https://man7.org/linux/man-pages/man1/iostat.1.html
- pidstat manual: https://man7.org/linux/man-pages/man1/pidstat.1.html
- sysstat project: https://sysstat.github.io/
Comments
0No comments yet. Be the first to comment!
Sign In to post a comment.