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

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.

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.

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.

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.

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:

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

Calls IOPMGetThermalWarningLevel and IOCopySystemLoadAdvisoryDetailed continuously. Since IOPMGetThermalWarningLevel doesn't do anything useful, this is mainly only good for getting a continuously updated number for CPU Speed limit.

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.