LinuxXXXIX · CPU PerformanceFrequency
CPU frequency, governors and thermal throttling - the clock is not constant
What you'll learn
- Identify the active cpufreq driver and governor, and the frequency range they permit
- Measure achieved frequency rather than nominal frequency, and explain why /proc/cpuinfo is unreliable for it
- Distinguish a governor decision from a thermal or power limit
- Explain why a guest VM has no cpufreq and what that means for benchmarking
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-11
Every CPU metric in this part - utilisation, run queue, context switches - assumes that a second of CPU time is worth the same amount of work as any other second. It is not. A core running at 800 MHz and the same core running at 4.9 GHz both report 100% utilisation while doing six times different amounts of work.
That gap explains a whole category of confusing results: the benchmark that is 40% slower on Tuesday, the batch job that takes twice as long on a hot afternoon, and the “identical” host that is not.
Which driver is in charge
Frequency on Linux is managed by a scaling driver plus a governor. Find both before anything else:
cpupower frequency-info --driver
cpupower frequency-info --policy
cpupower frequency-info --governors
driver: intel_pstate
current policy: frequency should be within 800 MHz and 4.90 GHz.
The governor "powersave" may decide which speed to use
within this range.
available cpufreq governors: performance powersave
The same facts are in sysfs, which is what you script against:
cd /sys/devices/system/cpu/cpu0/cpufreq
cat scaling_driver scaling_governor scaling_min_freq scaling_max_freq cpuinfo_max_freq
intel_pstate
powersave
800000
4900000
4900000
Frequencies in sysfs are in kHz. The drivers you will meet:
| Driver | Where | Notes |
|---|---|---|
intel_pstate | Modern Intel | In active mode offers only performance and powersave - it does its own internal scaling |
amd-pstate / amd-pstate-epp | Ryzen, EPYC | Equivalent for AMD; EPP variant exposes an energy/performance preference |
acpi-cpufreq | Older or firmware-driven | Full governor list including ondemand and conservative |
cppc_cpufreq | ACPI CPPC platforms, common on ARM servers | Firmware-abstracted |
| none | Virtual machines | The guest cannot see or set frequency at all |
Governors
Under acpi-cpufreq and intel_pstate in passive mode, the
governor list is longer:
performance- hold the highest permitted frequency. Predictable, and the right choice for latency-sensitive servers and for benchmarking.powersave- hold the lowest permitted frequency (onacpi-cpufreq).schedutil- the modern default on many distributions. Driven directly by scheduler utilisation signals, so it reacts faster than the older samplers.ondemand/conservative- older samplers that poll utilisation and ramp up quickly or gradually. Largely superseded byschedutil.userspace- the kernel sets whatever a userspace process writes. Only useful with a controlling daemon.
Set one for the current boot:
sudo cpupower frequency-set --governor performance
cpupower frequency-info --policy
current policy: frequency should be within 800 MHz and 4.90 GHz.
The governor "performance" may decide which speed to use
within this range.
This is runtime-only and lost at reboot. Persist it through your
distribution’s mechanism - /etc/default/cpupower with the
cpupower.service unit on Debian and Ubuntu, tuned profiles on
RHEL - rather than an rc.local echo, so a CPU that comes online
later gets the setting too.
Measure what you actually got
The nominal frequency is not the achieved frequency. Do not use
/proc/cpuinfo for this:
grep 'cpu MHz' /proc/cpuinfo | head -4
cpu MHz : 2495.322
cpu MHz : 2495.322
cpu MHz : 2495.322
cpu MHz : 2495.322
cpu MHz is a point sample taken at the instant you read the
file, on a value the kernel may not be updating at all. On this
host - a VM - it is simply the constant nominal clock and carries
no information about work done.
turbostat measures the real thing, by comparing cycles actually
executed against elapsed time:
sudo turbostat --quiet --show Busy%,Bzy_MHz,TSC_MHz,PkgWatt,PkgTmp --interval 5 --num_iterations 3
Busy% Bzy_MHz TSC_MHz PkgWatt PkgTmp
4.21 3902 2496 14.22 41
98.74 4780 2496 88.41 78
99.02 3310 2496 65.03 97
Three columns carry the story:
TSC_MHzis the invariant timestamp-counter rate. It is a constant and is not the clock the core ran at.Bzy_MHzis the average frequency while the core was busy. This is the number you want.Busy%is the fraction of time the core was not idle.
The three rows above are the whole lesson. Idle at 3.9 GHz. Loaded at 4.78 GHz - turbo engaged. Then still loaded, but dropped to 3.31 GHz with the package at 97 °C and power falling. Utilisation reads 99% in both of the last two rows while the work done per second fell by 30%.
Power policy or thermal limit?
Both reduce frequency, and the remedies are opposite: a policy problem is fixed with a setting, a thermal problem is fixed with airflow. Three signals separate them.
Temperature. Read the zones directly:
for z in /sys/class/thermal/thermal_zone*; do
printf '%s %s %s\n' "$z" "$(cat "$z/type")" "$(cat "$z/temp")"
done
/sys/class/thermal/thermal_zone0 acpitz 27800
/sys/class/thermal/thermal_zone1 x86_pkg_temp 97000
Values are millidegrees Celsius, so 97000 is 97 °C. A package sitting near its throttle point under load, and dropping when the load stops, is a cooling problem.
Kernel messages. Thermal events are logged:
sudo dmesg -T | grep -iE 'thermal|throttl|clock throttled'
[Tue Aug 11 14:02:11 2026] CPU4: Package temperature above threshold, cpu clock throttled (total events = 1)
[Tue Aug 11 14:02:11 2026] CPU4: Core temperature above threshold, cpu clock throttled (total events = 1)
[Tue Aug 11 14:03:44 2026] CPU4: Package temperature/speed normal
total events climbing across an incident window is conclusive.
Frequency limits imposed from outside. A datacentre chassis can cap frequency for power-budget reasons with no temperature problem at all. Compare the ceiling the kernel is allowed to request against the hardware maximum:
cd /sys/devices/system/cpu/cpu0/cpufreq
printf 'hw max %s kHz\nscaling max %s kHz\n' \
"$(cat cpuinfo_max_freq)" "$(cat scaling_max_freq)"
hw max 4900000 kHz
scaling max 2800000 kHz
A scaling_max_freq well below cpuinfo_max_freq that you did
not set is a BIOS or BMC power cap. That is a firmware
conversation, not a kernel one.
In a virtual machine, there is no cpufreq
Frequency is a host-level property. A guest sees a nominal clock and cannot see, measure or change what the physical core is doing:
cpupower frequency-info
analyzing CPU 4:
no or unknown cpufreq driver is active on this CPU
CPUs which run at the same hardware frequency: Not Available
CPUs which need to have their frequency coordinated by software: Not Available
maximum transition latency: Cannot determine or is not supported.
Not Available
available cpufreq governors: Not Available
Unable to determine current policy
current CPU frequency: Unable to call to kernel
boost state support:
Supported: no
Active: no
/sys/devices/system/cpu/cpu0/cpufreq/ does not exist at all,
and cpupower idle-info reports CPUidle driver: none.
Three consequences worth knowing:
- Do not benchmark absolute CPU performance in a guest and compare it against a different guest, or against the same guest last month. The physical frequency underneath is not controlled and not observable to you.
- Setting a governor in a guest is a no-op, including in configuration management. Roles that “set performance governor” silently do nothing on every virtualised host they touch, which is worth knowing before you attribute an improvement to them.
- Steal time is the guest-side signal. Since you cannot see
frequency,
%stealfrommpstatis the only visible evidence that the hypervisor is not giving you the CPU you asked for.
Frequency and the measurement discipline
This is the concrete reason a benchmark needs repeated runs. On bare metal with a dynamic governor:
- The first iterations run at a low frequency while the governor ramps - so a short run measures the ramp, not the workload.
- Sustained runs may start in turbo and settle lower as the package heats or as power budget is consumed, so a long run measures a changing machine.
- A neighbouring core going busy can reduce the turbo ceiling for yours, because turbo budgets are shared across the package.
For benchmarking specifically, pin the governor and remove the variable:
sudo cpupower frequency-set --governor performance
# Run the benchmark, several iterations, discarding warm-up.
sudo cpupower frequency-set --governor powersave
Record Bzy_MHz alongside the result. A benchmark number without
the frequency it was achieved at is not reproducible, and two
numbers that differ by 20% with no frequency recorded cannot be
compared at all.
Knowledge check
Knowledge check · 5 questions
Q1. Which turbostat column gives the frequency the core actually ran at while doing work?
Q2. A CPU throttled from 4.8 GHz to 2.1 GHz will show higher, not lower, utilisation in top.
Q3. On a host running intel_pstate in active mode, what does setting the governor to powersave do?
Q4. Which observations point at a thermal problem rather than a power policy? Select all that apply.
Q5. A configuration management role sets the CPU governor to performance across a fleet of virtual machines. What is the effect?
Passing score: 75%. Answers are checked in this browser.