Thermals and macOS

Dave MacLachlan
5 min readJan 10, 2020
Yes I know it’s not a Mac…

We build a lot of code for Apple platforms at Google, and spend a fair amount of time trying to optimize our builds. We have noticed that Macs are quite sensitive to thermals, and have done some work attempting to track the thermal characteristics of our machines and how that affects their build speeds. Here’s some info that we discovered that may be useful in your own explorations. The intended audience for this is someone quite familiar with macOS programming.

APIs

Apple has a plethora of APIs to monitor thermals on their platforms. Warning, your mileage may vary… Note also that I am focusing on userspace APIs. Kernel hackers have many more fun exciting and barely documented ways to get at thermal info.

IOPMGetThermalWarningLevel

IOPMGetThermalWarningLevel appears to have been introduced in macOS 10.7 and is documented to return one of three values:

  • kIOPMThermalWarningLevelNormal
  • kIOPMThermalWarningLevelDanger
  • kIOPMThermalWarningLevelCrisis

The implementation for IOPMGetThermalWarningLevel depends on SCDynamicStore looking for the ‘/IOKit/Power/ThermalWarning’ key. As far as I can tell, there is nothing that sets this key in System Configuration on macOS 10.14/10.15 so this call is useless (filed as Feedback FB7528845 — Update that this still appears to be broken on macOS 12.0.1).

IOGetSystemLoadAdvisory and IOCopySystemLoadAdvisoryDetailed

IOGetSystemLoadAdvisory andIOCopySystemLoadAdvisoryDetailed appear to have been introduced in macOS 10.6. IOGetSystemLoadAdvisory returns a simple value that is not a thermal measurement, but takes thermals into consideration. IOCopySystemLoadAdvisoryDetailed returns a dictionary containing the ‘kIOSystemLoadAdvisoryThermalLevelKey’ key. This key appears to contain one of these values:

  • kIOSystemLoadAdvisoryLevelBad = 1
  • kIOSystemLoadAdvisoryLevelOK = 2
  • kIOSystemLoadAdvisoryLevelGreat = 3

The implementation for IOCopySystemLoadAdvisoryDetailed also depends on SCDynamicStore, in this case looking for the ‘/IOKit/PowerManagement/SystemLoad/Detailed’ key. Contrary to the ThermalWarning key above, this is set.

IOPMCopyCPUPowerStatus

Returns a dictionary various CPU attributes.

  • kIOPMCPUPowerLimitProcessorSpeedKey
    CPU speed. This does get change when the machine gets hot and thermals come into play.
  • kIOPMCPUPowerLimitProcessorCountKey
    Apparently the number of processors that are currently active. See cpuctl below though for caveats.
  • kIOPMCPUPowerLimitSchedulerTimeKey
    Apparently controls job scheduling on CPUs have never seen it change, and not sure how to play with it.

NSProcessInfo

NSProcessInfo contains some useful thermal related functionality. You can register for the NSProcessInfoThermalStateDidChangeNotification, which alerts you to when you should check [[NSProcessInfo currentProcess] thermalState] for updates. -thermalState is documented to return the following values:

  • NSProcessInfoThermalStateNominal
  • NSProcessInfoThermalStateFair
  • NSProcessInfoThermalStateSerious
  • NSProcessInfoThermalStateCritical

This notification does work on macOS 10.15 and has been around since macOS 10.10.3.

OSThermalNotification.h

The very hopeful sounding OSThermalNotification.h appears at first glance to only support Apple’s mobile platforms. If you look carefully though you will see kOSThermalNotificationPressureLevelName which first appeared in macOS 10.10. It is just a string constant to be used with Darwin Notifications. See below for details.

Darwin Notifications [aka notify(3)]

The Darwin notification system has been around since macOS 10.3. It has a very detailed man page: man 3 notify. There are a couple of notification strings scattered around the headers that may be useful.

The most promising notification in IOKit is kIOPMThermalWarningNotificationKey. It’s likely that this was intended to be used to notify you when to call IOPMGetThermalWarningLevel. Unfortunately this notification never gets sent (as far as I can tell).

Other possibly useful notifications from IOKit are kIOSystemLoadAdvisoryNotifyName and kIOPMCPUPowerNotificationKey which alert you when to check IOCopySystemLoadAdvisoryDetailed and IOPMCopyCPUPowerStatus respectively. Note that if you don’t want to get the system load details for kIOSystemLoadAdvisoryNotifyName you can call notify_get_state to get the same value as IOGetSystemLoadAdvisory.

From OSThermalNotification.h we have kOSThermalNotificationPressureLevelName which returns one of the following values using notify_get_state:

  • kOSThermalPressureLevelNominal
  • kOSThermalPressureLevelModerate
  • kOSThermalPressureLevelHeavy
  • kOSThermalPressureLevelTrapping
  • kOSThermalPressureLevelSleeping

As it turns out this is what NSProcessInfoThermalStateDidChangeNotification uses under the hood, and the constants appear to map:

  • kOSThermalPressureLevelNominal = NSProcessInfoThermalStateNominal
  • kOSThermalPressureLevelModerate = NSProcessInfoThermalStateFair
  • kOSThermalPressureLevelHeavy = NSProcessInfoThermalStateSerious
  • kOSThermalPressureLevelTrapping = NSProcessInfoThermalStateCritical

Notifications are sent through /usr/sbin/notifyd and are posted by /usr/libexec/thermald.

Note: Another useful notification along these same lines is sent out when Low Power mode is activated via the “Battery” preference panel on a MacBook. The notification is is `com.apple.system.lowpowermode`. There doesn’t appear to be an official public constant defined for it (FB9742239). The state (on/off) can be determined using notify_get_state.

Testing

So how can we test macOS thermal code? (short of wrapping your machine up in a jacket…which we did for a while). With Xcode 11.3 Apple has introduced ways to force thermal profiles on iOS devices, but nothing exists for the Mac.

It turns out that there are a variety of command line utilities that will give you information about thermals:

powermetrics

Returns a huge amount of information about power and thermals. Probably overkill in most cases.

cpuctl

A little known (as far as I can tell) command that lets you control the number of processors available at any given time. It has a man page, and from what I can tell does actually turn processors off. You can see it using Activity Monitor and its pretty CPU graphics. The Instruments.app preferences will also tell you that CPUs are disabled (and is another way of disabling CPUs).

Two CPUs disabled

That being said, turning off a processor via either cpuctl or instruments does not appear to send a notification via kIOPMCPUPowerNotificationKey regarding a CPU being disabled (FB9738489–macOS 11.6 and 12.01)

pmset -g thermlog

Sits waiting on notifications via kIOPMThermalWarningNotificationKey and kIOPMCPUPowerNotificationKey.Since thermal notifications via kIOPMThermalWarningNotificationKey never get sent and CPU count never changes, this is mainly only good for getting a continuously updated number for CPU Speed limit.

thermal

thermal does not have a man page, but running it without any arguments gives some basic “help”. There’s some real goodness in here allowing you to simulate thermal pressure notifications, get current levels and get the thermal configuration for your machine. It turns out that these configurations are all stored in /System/Library/Extensions/IOPlatformPluginFamily.kext/Contents/PlugIns/X86PlatformPlugin.kext/Contents/Resources/ and are keyed by a per motherboard UUID. The configurations define the levels at which notifications are going to get sent.

For what it’s worth I have never been able (willing) to force my machines to report any real value higher than kOSThermalPressureLevelModerate which is pretty surprising given that I have gotten my CPU Speed Limits down below 50. As mentioned above I had my MacBook Pro 16" doing a full build of YouTube while running two YouTube videos while wrapped in a fleece jacket, and that wasn’t sufficient to push the thermal levels above 120 (according to thermal levels). My fans were going full bore, I was afraid of doing damage, and my office mates were getting annoyed. According to Apple at WWDC 2015 you only need to worry once you reach NSProcessInfoThermalStateSerious and above, but in my experience so far you are unlikely to ever reach these levels.

Conclusions

Use notify with kOSThermalNotificationPressureLevelName (or the higher levelNSProcessInfoThermalStateDidChangeNotification). Test using thermal.

--

--