MacThrottle: reading the private thermal pressure signal macOS does not expose
Menu bar app to know when your Mac is thermal throttling
At a glance
- What is it?
- MacThrottle is a SwiftUI menu bar app for macOS 15 that shows thermal pressure, CPU temperature and fan speed, and it gets its five-level pressure reading from an undocumented Darwin notification rather than the public ProcessInfo API. It suits Mac owners who want to know why a machine slowed down, and it depends on a private interface that Apple could change at any time.
- Who is it for?
- MacThrottle is worth installing for a Mac owner on Apple silicon who wants to know whether a slow build or a fan-spinning session is thermal, because it reads the same five-level pressure signal the system itself uses, and because the public API it replaces cannot distinguish the two states where throttling actually begins. It is the wrong tool for a machine with no SMC access or outside macOS 15, and the wrong dependency for a product that cannot carry a private API.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 68 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A menu bar thermometer for a state macOS only reports coarsely
MacThrottle is a lightweight macOS menu bar app that monitors thermal pressure and alerts you when your Mac is being throttled, with no admin privileges required. The problem it solves is small and real: the public way to ask macOS how hot the machine is does not tell you enough.
The features are concrete. Thermal pressure state appears in the menu bar as different thermometer icons, CPU core temperature is shown, read directly from the SMC, and fan speed appears as a percentage on Macs that have fans. There is a history graph covering thermal state, temperature and fan speed over the last ten minutes, and a statistics view of how much time was spent in each thermal state.
The notifications are configurable, and the set of them tells you what the author considered worth knowing. You can be told when heavy throttling begins, when critical throttling occurs, and when throttling stops, which is the recovery case that most people never notice happening. Notification sounds are optional, and there is a Launch at Login option so the app is already running when you want to know.
The design constraint that shapes everything else is stated in the feature list as its own line: no helper daemon and no admin privileges required. That rules out the privileged helper approach that some similar tools use, and it is why the app can sit in the menu bar doing nothing at all most of the time.
The pressure signal comes from a Darwin notification Apple never documented
The mechanism for reading thermal pressure is the Darwin notification system, specifically notify_get_state with the com.apple.system.thermalpressurelevel notification. The system reports five levels, nominal, moderate, heavy, trapping and sleeping, and MacThrottle consolidates the last two into a single critical state, because the documentation says they are rarely reached in practice.
What makes this interesting is the provenance. The notification name is not publicly documented by Apple. It comes from a private OSThermalNotification.h header, where it appears as kOSThermalNotificationPressureLevelName, and it has been available since macOS 10.10. The documentation also points at an existing implementation in Bazel's native code as a reference for how the value is read.
So the app is built on an interface that exists, is stable in practice, and is entirely undocumented. That is a deliberate trade rather than an oversight, and it is worth being clear about what it means: the app is reading a private signal on the user's own machine, and there is no guarantee from Apple that the name or the semantics will not change.
The tool is useful precisely because the public API is not granular enough, which brings us to the next point.
ProcessInfo.thermalState collapses moderate and heavy into one value
macOS does provide a public API for this, ProcessInfo.thermalState, and the documentation explains at length why the app does not use it. The reason is granularity: the public enum has four states, while the actual pressure signal has five levels, and the mapping loses information.
The table makes the loss concrete. Nominal maps to nominal. Fair maps to moderate. Fair also maps to heavy. Serious maps to trapping, and critical maps to sleeping.
The second row is the problem. Both moderate and heavy appear as fair in the public API, and the documentation says the difference is significant, because heavy is where throttling really kicks in. A user watching ProcessInfo.thermalState would see the same value before and after the point where the machine starts slowing down, and would have no way to tell the two states apart.
That is the whole justification for the private API, and it is a good one. The trade is that MacThrottle gives you the information Apple does not expose, at the cost of depending on a name Apple has not promised to keep.
CPU temperature comes from the SMC, with a coarser fallback
Temperature is read by two methods, and the primary one is the System Management Controller, a hardware chip in every Mac that manages thermal sensors, fans and power. MacThrottle reads directly from the SMC via IOKit to get actual CPU core temperatures, using chip-specific sensor keys that differ for M1, M2, M3 and other generations. The approach is based on the one used by the Stats application.
The fallback is the IOHIDEventSystem private API, used if the SMC read fails. It reads temperature events from the PMU, the Power Management Unit, sensors. The documentation is honest about the difference: the fallback is simpler but less granular, returning aggregate tdie temperatures rather than per-core values, and it may report slightly different, typically lower, values than the SMC.
The displayed temperature is the maximum reading across all available CPU sensors, so it represents the hottest core rather than an average. That choice is right for the purpose, since the question a user is asking is whether something is running hot, not what the typical core temperature is.
The fallback existing at all is the point worth noting. On a chip generation whose SMC sensor keys the app does not know, the temperature readout degrades rather than disappears, which is a materially better failure mode than showing nothing.
Fan speed is a percentage of maximum, read from standard SMC fan keys
On Macs with fans, the speed is read from the SMC using the standard fan keys: FNum for the count of fans, F0Ac and F1Ac for the actual RPM, and F0Mx and F1Mx for the maximum RPM.
What is displayed is the actual speed as a percentage of maximum capacity, and on the history graph it appears as a dashed cyan line. So the graph carries three series at once, thermal state, temperature and fan speed, and the fan line is the one that explains the other two.
Fanless Macs, such as the MacBook Air, simply have no fan data, and the documentation notes that the Show Fan Speed toggle will not appear for them. That is the correct behaviour: a toggle for a sensor that cannot exist is worse than no toggle.
The combination is what makes the app useful rather than decorative. Thermal pressure tells you the machine is under strain. Temperature tells you how hard it is working. Fan speed tells you whether the cooling system is keeping up. Only the first is available from a public API, and only the first is what most monitoring tools show.
Installing it, and the Gatekeeper problem with an unsigned build
The recommended install is Homebrew, through a tap of the author's:
brew tap angristan/tap
brew install --cask macthrottleThe alternative is to download the latest .dmg from Releases, open it, and drag MacThrottle.app into the Applications folder. Because the app is not signed, Gatekeeper may block it on first launch, and the documented workaround is to remove the quarantine attribute manually with xattr, or to build it locally so it is signed with your own certificate.
Building locally is a short xcodebuild invocation, and it signs the app with your development certificate, which avoids the Gatekeeper issue entirely:
# Clone the repo
git clone https://github.com/angristan/MacThrottle.git
cd MacThrottle
# Build with Xcode
xcodebuild -project MacThrottle.xcodeproj \
-scheme MacThrottle \
-configuration Release \
-derivedDataPath build
# Run the app
open build/Build/Products/Release/MacThrottle.appThe repository also ships a Makefile with dev, build, kill, open and clean targets for the debug cycle, where the dev target chains build, kill and open, and the clean target removes the .build directory and runs an xcodebuild clean.
The requirement is macOS 15.0 or later, Sequoia. The badges in the readme state macOS 15+ and Swift 6, and the project is a SwiftUI application with a .swiftlint.yml configuration and a MacThrottle.xcodeproj in the tree.
Where MacThrottle is the wrong tool
The first limit is the platform, and it is absolute. This is a macOS menu bar app. There is no Linux or Windows version, and the underlying mechanisms, the SMC over IOKit and the Darwin notification, are macOS specific.
The second limit is the private API dependency, and it applies more strongly to anyone building on this than to a user. For an individual, an undocumented notification that has been present since macOS 10.10 is a reasonable thing to depend on. For a product that ships to other people, it is a liability, because the entire value proposition of the app rests on a name Apple has not documented. The readme is upfront about this, and the fact that it names the private header and links an independent implementation is a mark of honesty rather than a sign that the risk is small.
The third limit is what it does not measure. It reports thermal pressure, CPU temperature and fan speed. It does not report which process is causing the heat, and it does not report power consumption or GPU temperature as separate series. If your question is which of your running applications is pushing the machine into a heavy throttling state, this app will not answer it.
The fourth is the fallback quality. If the SMC read fails on your chip generation, the temperature display degrades to an aggregate reading that may be lower than the truth. That is fine as a graceful degradation, but it means the number on screen is not equally trustworthy on every machine.
The alternative for a user who only wants the number is the public API inside their own code, or a general system monitor that reports processor load. The difference in approach is what MacThrottle adds: it reads the signal at the level where the operating system makes the throttling decision, so it can show the state that the public API hides.
Maintenance and the shape of the release line
The project is not archived, and the last push was on 2026-08-16, the same day as the v1.9.2 release. The release history is dense: v1.9.0 on 2026-08-08, v1.9.1 on 2026-08-10, and v1.9.2 on 2026-08-16. Three patch releases in eight days is the pace of a project that is shipping small fixes as they land rather than batching them.
The tooling in the tree is worth noting for anyone who wants to contribute or build from source. There is a .releaserc.json, which points to a semantic-release driven release process, and a commitlint.config.mjs, so the commit message format is enforced. A .swiftlint.yml configures the Swift linter, there is a scripts/ directory, a CHANGELOG.md is maintained, and a Makefile handles the local build cycle as described above.
The licence is MIT, which is about as simple as a licence gets and puts no conditions on use, modification or redistribution beyond keeping the notice.
The homepage points at a blog post about building a macOS thermal throttling app, at stanislas.blog, which is where the reasoning behind the design decisions described here is set out at length.
Editorial conclusion
MacThrottle is worth installing for a Mac owner on Apple silicon who wants to know whether a slow build or a fan-spinning session is thermal, because it reads the same five-level pressure signal the system itself uses, and because the public API it replaces cannot distinguish the two states where throttling actually begins. It is the wrong tool for a machine with no SMC access or outside macOS 15, and the wrong dependency for a product that cannot carry a private API. Before you rely on it, check the three things that decide how much you should trust it: that your Mac meets the macOS 15.0 requirement, since the app targets Sequoia; that the SMC path works on your specific chip, since the sensor keys differ by generation and the fallback returns a coarser aggregate reading; and that the pressure notification continues to exist, since the documentation is explicit that the name is not publicly documented by Apple and comes from a private header.
Frequently asked questions
How do I install MacThrottle on macOS?
The recommended route is Homebrew with brew tap angristan/tap followed by brew install --cask macthrottle. You can also download the latest .dmg from the Releases page and drag MacThrottle.app into Applications, removing the quarantine attribute with xattr if Gatekeeper blocks the unsigned build, or build locally with xcodebuild to sign it with your own certificate. macOS 15.0 or later is required.
How does MacThrottle know when a Mac is thermally throttled?
It reads the com.apple.system.thermalpressurelevel Darwin notification with notify_get_state, which reports five levels, and consolidates trapping and sleeping into a single critical state. The notification name is not publicly documented by Apple and comes from a private OSThermalNotification.h header, and it has existed since macOS 10.10.
Why not use ProcessInfo.thermalState in MacThrottle?
The public API has only four states and maps both moderate and heavy pressure to fair, hiding the point where throttling really begins. MacThrottle uses the Darwin notification instead so it can report the heavy state separately from moderate.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/angristan-macthrottle)Was this useful?