Read /proc/stat for CPU and System Counters
You will finish with a practical way to read /proc/stat, calculate CPU utilisation from two snapshots, and inspect the system-wide counters that explain what the kernel has been doing. The examples use the installed Linux man-pages 6.7 documentation and the live file on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell and read access to /proc, which is normally available to an ordinary user. This guide only reads pseudo-files and does not change kernel settings, services or processes. No elevated privileges are required.
1. Take a snapshot and identify the lines
Start by printing the file. It is a virtual, changing report, not a configuration file stored on disk:
$ sed -n '1,18p' /proc/stat
cpu 67642011 1399945 19104579 904614526 33075204 0 727012 0 0 0
cpu0 8216369 188400 2446017 113173955 4209895 0 29745 0 0 0
intr 6311616781 48 4 0 0 0 0 0 0 0 0 0 ...
ctxt 11969158000
btime 1789138716
processes 92481573
procs_running 11
procs_blocked 7
softirq 3509394863 4 307798694 3877200 ...
Your numbers will differ. The first cpu line is the aggregate for all CPUs; cpu0, cpu1 and so on are per-CPU counters. The remaining names are system-wide counters. Use a complete line when parsing, rather than assuming this sample's number of CPUs or interrupt columns.
Checkpoint: confirm the file is readable and that the aggregate line exists:
$ awk '$1 == "cpu" { print; found = 1; exit } END { if (!found) exit 1 }' /proc/stat
The counters are cumulative since boot. A single snapshot tells you totals, but CPU percentage and rates require two snapshots separated by a known interval.
2. Check the counter unit before doing maths
The CPU fields are measured in USER_HZ, often called jiffies. The installed manpage says this is 1/100th of a second on most architectures and recommends sysconf(_SC_CLK_TCK) when a program needs the exact value. Ask the machine rather than assuming:
$ getconf CLK_TCK
100
This value converts a CPU counter delta into CPU-seconds: divide by CLK_TCK. For a percentage over an interval, you do not need to convert each field. Divide the busy delta by the total delta, then multiply by 100.
3. Calculate total CPU use from two samples
The aggregate CPU fields are, in order: user, nice, system, idle, iowait, irq, softirq, steal, guest and guest_nice. The first four and the later additions are not interchangeable labels. Keep the field positions intact.
This small shell script takes two readings one second apart. It reports busy time as everything except idle and iowait, a common monitoring convention:
read_cpu() {
awk '$1 == "cpu" {
total = 0
for (i = 2; i <= NF; i++) total += $i
idle = $5 + $6
print total, idle
exit
}' /proc/stat
}
set -- $(read_cpu)
total1=$1
idle1=$2
sleep 1
set -- $(read_cpu)
total2=$1
idle2=$2
total_delta=$((total2 - total1))
idle_delta=$((idle2 - idle1))
busy_delta=$((total_delta - idle_delta))
if [ "$total_delta" -le 0 ] || [ "$busy_delta" -lt 0 ]; then
printf '%s\n' 'counter interval was invalid' >&2
exit 1
fi
awk -v busy="$busy_delta" -v total="$total_delta" \
'BEGIN { printf "CPU busy: %.1f%%\n", 100 * busy / total }'
Typical output is one line such as CPU busy: 7.4%. It will vary with workload. The script deliberately treats iowait as idle because waiting for I/O is not active CPU execution. The manpage warns that iowait is not reliable: the value can decrease and is difficult to attribute on multi-core systems.
Checkpoint: run the same calculation twice while the machine is quiet. Similar results are more useful than a single reading. If you need per-core utilisation, replace the aggregate match with a match for a specific name such as cpu0, then apply the same delta logic.
4. Interpret the other counters
These lines are useful for lightweight diagnostics and for checking that a monitoring exporter is reading the right source:
| Line | Meaning | How to use it |
|---|---|---|
page | Pages paged in and out from disk. | Compare deltas over time; it is a signal, not a complete memory diagnosis. |
swap | Swap pages brought in and out. | Look for sustained movement alongside memory and I/O metrics. |
intr | Total interrupts, followed by per-interrupt counts. | Use the first value for an overall rate; columns after it are architecture and kernel dependent. |
ctxt | Context switches since boot. | Calculate a rate from two samples when investigating scheduling activity. |
btime | Boot time as seconds since the Unix Epoch. | Convert it with date rather than treating it as an uptime value. |
processes | Forks since boot. | Use the delta to spot process creation bursts. |
procs_running and procs_blocked | Runnable processes and processes waiting for I/O. | Read them as a point-in-time queue indication, not a historical count. |
softirq | Total softirqs followed by per-type counts. | Use deltas when investigating interrupt-heavy workloads. |
For example, convert the boot timestamp without changing anything:
$ boot_seconds=$(awk '$1 == "btime" { print $2; exit }' /proc/stat)
$ date -d "@$boot_seconds" '+%Y-%m-%d %H:%M:%S %Z'
2026-09-11 15:58:36 BST
The displayed date is host-specific and can change with the machine clock and timezone. On systems without GNU date, use a suitable Epoch-time converter instead. Do not compare btime directly with /proc/uptime: one is an Epoch timestamp and the other is elapsed seconds.
5. Avoid the common parsing traps
Do not hard-code the number of fields after cpu. Linux has added fields over time, and the manpage records when several appeared: iowait in Linux 2.5.41, interrupt and softirq fields in the 2.6 series, virtualisation fields for guest time, and per-system softirq data from Linux 2.6.31. A parser should accept extra fields and identify the fields it understands by position and documented kernel version.
Guest time is already accounted for in user and nice time on Linux. If you add every CPU field as independent busy work, you can count guest execution twice. Keep your convention explicit, especially when comparing your result with a library or exporter.
Values can wrap or reset after a reboot, and a counter can move backwards in exceptional cases. Reject negative deltas and start a new baseline. Never use a counter from before a reboot with one from after it. The read itself is unprivileged, but do not treat these values as a security boundary: they describe system activity and may reveal operational details.
Done means
- You can distinguish the aggregate
cpuline from per-CPU lines. - You checked
CLK_TCKinstead of assuming the unit. - You calculated utilisation from deltas, not from one counter snapshot.
- You know that
iowait, guest time and variable field counts need careful interpretation. - You can turn the other lines into rates or timestamps without modifying the system.