Diagnosing CPU Thermal Throttling and Frequency Scaling on a VPS: A Measurement Method

A VPS that benchmarks well on a fresh boot and degrades over a long run has a specific class of cause: the CPU is not running at the frequency you assume. Hypervisor-level cgroup CPU quotas, physical host thermal throttling, and cpufreq governor behaviour all reduce delivered cycles, and none of them shows up in top. This article shows how to detect and quantify lost CPU performance on a virtualized guest, separating throttling you control from throttling the host imposes.

First: is the frequency even exposed?

Many VPS expose cpufreq as read-only or not at all — the hypervisor owns the real frequency scaling and the guest sees a static nominal clock. Check whether you have any visibility:

lscpu | grep -i mhz
cpupower frequency-info 2>/dev/null
ls /sys/devices/system/cpu/cpu0/cpufreq/ 2>/dev/null
grep MHz /proc/cpuinfo

If /proc/cpuinfo MHz is frozen at the nominal value while load varies, the frequency is virtualized and you are seeing a fixed number — the guest cannot measure real host frequency from here. That is not a dead end; you switch to measuring delivered work per unit time instead.

Measure delivered throughput, not clock

When frequency is hidden, the reliable proxy is a CPU-bound workload whose instruction count you control, timed precisely. Run it in a loop and watch for a slope:

for i in $(seq 1 20); do
  /usr/bin/time -f "%e wall %U user"     openssl speed -seconds 2 sha256 2>&1 | tail -1
  sleep 5
done

A stable host shows constant numbers. A host that throttles under sustained load shows throughput falling across iterations and a growing gap between user time and wall time. The user/wall ratio is the key signal: if the process burns 2.0 s of user CPU but takes 3.5 s of wall time, it is being descheduled or starved of cycles.

Distinguish steal, quota, and thermal

Three mechanisms reduce CPU and they need different responses. Read the breakdown from vmstat or /proc/stat:

vmstat 1 10            # us, sy, id, wa, st columns
grep '^cpu ' /proc/stat
  • Rising st (steal): the hypervisor gave your vCPU to another tenant. You cannot fix this from inside — it is a noisy-neighbour symptom.
  • Low steal, high us, but throughput falling with time: physical host thermal or power throttling. Invisible to you except via the delivered-throughput slope.
  • Periodic stalls with idle CPU: a cgroup CPU quota (cpu.max) throttling your container. Confirm with PSI CPU pressure.

Confirm cgroup CPU throttling from inside the guest

cat /sys/fs/cgroup/cpu.stat          # nr_throttled, throttled_usec
cat /proc/pressure/cpu
cat /sys/fs/cgroup/cpu.max 2>/dev/null

If nr_throttled and throttled_usec climb while your workload is CPU-bound, a quota is capping you: you have burst capacity but a hard ceiling. This is common on shared plans. The fix is either raising the quota (a plan change) or reducing how long you saturate the core.

Frequency scaling governors you control versus ones you do not

If your provider exposes cpufreq, the governor decides how the host’s physical cores ramp. performance pins to maximum frequency, powersave stays low until load demands more, and schedutil scales based on scheduler utilization. In a virtualized guest, changing this usually has no effect because the hypervisor owns it — but some bare-metal-like VPS plans do expose it, and then setting performance removes ramp-up latency for latency-sensitive services. Check with cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor. If the directory exists and is writable, you can influence frequency; if it does not, you cannot, and any “governor tweak” advice you read is irrelevant to your instance. Knowing which world you are in prevents a lot of wasted effort.

Building a simple throttling dashboard

You do not need a full monitoring stack to catch this. A one-minute cron writing three numbers — steal percentage from /proc/stat, nr_throttled delta from cpu.stat, and memory-PSI average — is enough to correlate throttling with load. Plot or just log them and look for the pattern: steal that tracks your own load is a neighbour problem; throttling that starts exactly when you sustain CPU is your quota; delivered-throughput decay over a long run with flat steal is thermal. Each pattern points at a different fix, and without the data you will guess wrong. The discipline is the same as any performance work on this site: measure the symptom, attribute it to one of the three mechanisms, then change exactly one thing and re-measure. At that point the honest conclusion is that the instance itself is the ceiling.

Why sustained tests reveal what short tests hide

A five-second benchmark never triggers thermal throttling. Heat builds over tens of seconds to minutes as the die saturates, and the frequency drop happens after your test has finished. The same is true of CPU quotas: a burstable instance grants full cores briefly, and only a sustained run exposes the ceiling. This is why the single most useful diagnostic is a long, boring, repeatable test — ten minutes of fixed work sampled every thirty seconds — rather than a fast one that flatters the hardware. Record the delivered work per interval and watch the trend, not the peak. If the numbers fall and then plateau, you are seeing thermal throttling reach equilibrium. If they oscillate on a fixed period, you are seeing a quota refill cycle. If they are flat but low from the start, the instance simply does not deliver the cores you think it does, and no amount of tuning will change that.

Put it together

Run the timed workload, watch vmstat steal and cpu.stat throttling simultaneously, and you can attribute lost performance to one of the three causes within a few minutes. If it is steal, you need a plan with dedicated or guaranteed cores — our performance VPS plans lists which tiers include pinned vCPU rather than burstable shared CPU. If it is your own quota, right-size the plan or move the burst off the critical path. Either way, stop trusting top’s instantaneous percentages: they hide throttling that only appears over sustained load. When steal time is the cause and you cannot fix it from inside the guest, our VPS comparison page is the starting point for moving to an instance that does not oversubscribe its cores.

Leave a Reply