(X)ubuntu cpufreq and cpufreqd (adventures in cooling)

My long-serving and, until now, totally reliable laptop started to misbehave last week.  Working from home demanded that I install some new software, which I initially blamed for the system crashes.  But the problem persisted when I removed the newcomers and a little digging revealed that the processor was overheating.

Under normal circumstances I would open up the laptop, give it a good clean and, if possible, renew the thermal compound on the heatsink(s).  But, given that at the time of writing the UK is under COVID-19 lockdown, these are far from normal circumstances.  I didn’t want to risk breaking the laptop and so my pragmatic solution was to throttle back the CPU and reduce the heat output.

Manual processor scaling with cpufrequtils

I’ve used CPU throttling in Ubuntu for a long time.  The cpufrequtils package provides the cpufreq-info and cpufreq-set tools.  I have a few bash scripts in my home directory that allow me to quickly change the CPU’s performance.  Here’s my script for when I’m writing on the move and want to maximise battery life:

#!/bin/bash
sudo cpufreq-set --cpu 0 --governor powersave --min 800MHz --max 800MHz
sudo cpufreq-set --cpu 1 --governor powersave --min 800Mhz --max 800MHz
sudo cpufreq-set --cpu 2 --governor powersave --min 800MHz --max 800MHz
sudo cpufreq-set --cpu 3 --governor powersave --min 800MHz --max 800MHz

It’s fairly self-explanatory, with a command for each ‘CPU’ (in this case, physically speaking, there are two cores and four threads which are presented in Ubuntu as four CPUs).  The governor choices can be found using cpufreq-info and the minimum and maximum values depend on CPU capabilities and personal preference.  The above script uses the powersave governor and throttles the CPU at its minimum operating frequency (–min and –max both set to 800MHz) in order to keep the laptop running as long as possible.

Finding the right balance

I needed to balance reliability with performance.  800MHz gives low temperatures but not the kind of computing power that video calls using Zoom and Microsoft Teams (the causes of my problems) need.  I used three separate XFCE (I’m running Xubuntu) Terminal Emulator windows for my tests:

  1. To run my control scripts.  I used this window to edit and run my experimental script, where I adjusted the maximum CPU frequency.  I also wanted to be able to quickly run my ‘maximum battery’ script when temperatures became too high.
  2. For the stress tool (available in the stress package, more about this below) used to occupy the CPU.
  3. Finally, a window to run the sensors command (from the lm-sensors package) so that I could monitor the CPU temperature.

For my experimental script I kept using the powersave governor, for a less aggressive approach to ‘throttling up’ the CPU than the performance governor also available for my laptop’s CPU.  This works fine for me.  I then began with the maximum frequency for the CPU (see Intel’s ‘ark’ page for the i3-2330M if you’re interested) and ran the stress tool.  For this processor, with its 4 threads, the command was:

stress -c 4

Running htop (htop package) in another terminal window confirmed that all four ‘CPUs’ were running at 100%.

The sensors command showed the CPU core temperatures rapidly approaching the maximum temperature for the CPU (100ºC, or 212ºF).  At this point I quickly ran my ‘safety net’ script to throttle down the CPU.

It was then a case of reducing the –max value in the cpufreq-set commands of my experimental script until the temperature was acceptable.  To my surprise it didn’t take a massive reduction, with 1.9GHz resulting in a maximum temperature of around 83ºC (181ºF).  In the interest of safety, along with less fan noise, I settled on 1.7GHz (76ºC, 169ºF).  My final cooling.sh script looked like this:

#!/bin/bash
sudo cpufreq-set --cpu 0 --governor powersave --min 800MHz --max 1.7GHz
sudo cpufreq-set --cpu 1 --governor powersave --min 800MHz --max 1.7GHz
sudo cpufreq-set --cpu 2 --governor powersave --min 800MHz --max 1.7GHz
sudo cpufreq-set --cpu 3 --governor powersave --min 800MHz --max 1.7GHz

Automatic scaling with cpufreqd

All was working well, but running cooling.sh is a manual task and I am forgetful.  The first heat-related system crash during a video call suggested the need for automation.  A quick web search revealed cpufreqd (same package name), which is a daemon (service) that automatically controls the CPU frequency.  The man pages for cpufreqd and cpufreqd.conf (the configuration file) are clear enough.  Here is my finished /etc/cpufreqd.conf file:

# /etc/cpufreqd.conf
# Last edited: 1 April 2020

# ----------------------------------------------------------------------
# General settings

[General]

# Process ID (PID) file for cpufreqd
pidfile=/var/run/cpufreqd.pid

# How often to read the system status (seconds)
poll_interval=2

# Verbosity level (4: warning/error/critical messages only)
verbosity=4

# Open a local socket (listen for tools such as cpufreqd-get and cpufreqd-set)
# enable_remote=1

# Restrict access to socket
# remote_group=root

[/General]


# ----------------------------------------------------------------------
# PROFILES

[Profile]
name=powersave_minimum
minfreq=800000
maxfreq=800000
policy=powersave
[/Profile]

[Profile]
name=powersave_fullrange
minfreq=800000
maxfreq=2200000
policy=powersave
[/Profile]

[Profile]
name=powersave_cooling
minfreq=800000
maxfreq=1700000
policy=powersave
[/Profile]


# --------------------------------------------------------------------------------
# RULES

[Rule]
name=ac_normal
ac=on
profile=powersave_cooling
[/Rule]

[Rule]
name=battery
ac=off
profile=powersave_minimum
[/Rule]

There are some general options, then come the profiles and rules.  Profiles define CPU  behaviour and rules decide when the profiles are used.  My initial configuration is very basic, with the presence or absence of mains power (the ‘ac’ directive in the two rules above) controlling the CPU speed.

For testing purposes I stopped the cpufreqd service and used foreground mode (cpufreqd -D).  Running cpufreq-info in a separate terminal window revealed that the CPU speed scaled correctly in response to changes in power source.

These two rules cover my needs pretty well, although cpufreqd is far more capable than this.  I created a third profile (see ‘powersave_fullrange’ in the above script) which I would like to be the default behaviour on mains power, with ‘powersave_cooling’ as a fallback profile when the CPU temperature exceeds 80ºC (176ºF).  This will give me the full power of the CPU for bursty activities, but the protection of lower performance for prolonged periods of high processor usage.  Unfortunately the sensors plugin included with cpufreqd is struggling to deal with the sensor information provided by my laptop.  I will post an update if this is resolved.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s