Skip to main content
RunBook Academy

LinuxXXXIX · CPU PerformanceFrequency

CPU frequency, governors and thermal throttling - the clock is not constant

Advanced⏱ ~19 mincpupowerturbostat

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

Not yet marked complete on this device.

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:

DriverWhereNotes
intel_pstateModern IntelIn active mode offers only performance and powersave - it does its own internal scaling
amd-pstate / amd-pstate-eppRyzen, EPYCEquivalent for AMD; EPP variant exposes an energy/performance preference
acpi-cpufreqOlder or firmware-drivenFull governor list including ondemand and conservative
cppc_cpufreqACPI CPPC platforms, common on ARM serversFirmware-abstracted
noneVirtual machinesThe 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 (on acpi-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 by schedutil.
  • 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_MHz is the invariant timestamp-counter rate. It is a constant and is not the clock the core ran at.
  • Bzy_MHz is 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, %steal from mpstat is 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

  1. Q1. Which turbostat column gives the frequency the core actually ran at while doing work?

  2. Q2. A CPU throttled from 4.8 GHz to 2.1 GHz will show higher, not lower, utilisation in top.

  3. Q3. On a host running intel_pstate in active mode, what does setting the governor to powersave do?

  4. Q4. Which observations point at a thermal problem rather than a power policy? Select all that apply.

  5. 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.