macmon is a sudoless performance monitor for Apple Silicon Macs. It reads real-time CPU / GPU / ANE power usage, temperatures, and memory stats through a private macOS API β the same data powermetrics exposes β without requiring root access.
- π« Runs without sudo
- β‘ Real-time CPU / GPU / ANE power usage
- π CPU frequency-scaled and active ratios per cluster
- πΎ RAM / Swap usage and memory pressure
- π Historical charts with average and max values
- π‘οΈ Average CPU / GPU temperature
- π Process list with per-process CPU, memory, power and GPU usage
- π¨ Follows your terminal's color scheme
- πͺ Can be displayed in a small window
- π¦ Written in Rust
brew install macmonOther installation methods
Install using MacPorts:
sudo port install macmonInstall using Cargo:
cargo install macmonInstall using Nix:
nix-env -i macmonTested on macOS 14 and later; older versions may work too.
Usage: macmon [OPTIONS] [COMMAND]
Commands:
pipe Output metrics in JSON format
serve Serve metrics over HTTP
debug Print debug information
stress Generate load for testing metrics
help Print this message or the help of the given subcommand(s)
Options:
-i, --interval <INTERVAL> Update interval in milliseconds [default: 1000]
-h, --help Print help
-V, --version Print versionRun macmon without a subcommand to open the terminal UI: the metric boxes on top and the process list below. Press ? in the app to see the keys.
When the TUI runs as root, preference changes apply only to that session and are not saved. Run without sudo to persist settings.
Press / to filter the process list: commas separate alternatives, spaces combine words, ! negates a word. For example chrome, !helper, node mem:500m !user:root or app:safari, which shows the Safari app with its helpers. A plain word looks in the name or pid. Keys take a number, above it or below it with <: cpu:, gpu:, power:, mem: (k, m, g) and time: (s, m, h, d); or text: user:, path:, dir: (the working directory), cmd: (the command line, of your own processes) and app: (the name of the .app the process or its responsible process runs from); or an exact local TCP or UDP port, of your own processes: port:3000. Enter keeps the filter, Esc clears it.
Press Space to pause the process list: rows keep their order while values update, new processes wait until you resume, and the files list of i stops updating. Press f to turn the filter off and on without losing its text.
You can use the pipe subcommand to output metrics in JSON format, which makes it suitable for piping into other tools or scripts. For example:
macmon pipe | jqThis command runs macmon in "pipe" mode and sends the output to jq for pretty-printing.
You can also specify the number of samples to collect using the -s or --samples parameter (default: 0, which runs indefinitely), and set the update interval in milliseconds using the -i or --interval parameter (default: 1000 ms). For example:
macmon pipe -s 10 -i 500 | jqThis will collect 10 samples with an update interval of 500 milliseconds.
You can use the serve subcommand to expose metrics over HTTP. This is useful for integrating with monitoring systems like Prometheus and Grafana.
macmon serve # default port 9090, interval 1000ms
macmon serve --host 127.0.0.1 # listen on localhost only
macmon serve -p 8080 # custom port
macmon serve -i 500 # sampling interval 500ms
macmon serve & # run in backgroundTwo endpoints are available:
| Endpoint | Format | Description |
|---|---|---|
GET /json |
JSON | Current metrics snapshot (same format as pipe --soc-info) |
GET /metrics |
Prometheus | Metrics in Prometheus text format |
To start macmon serve at boot, with nobody logged in, and keep it running:
sudo macmon serve --install # install and start (default port 9090)
sudo macmon serve --port 8080 --install # with custom port
sudo macmon serve --host 127.0.0.1 --install # listen on localhost only
sudo macmon serve --uninstall # stop and remove--install needs sudo: it creates a LaunchDaemon at /Library/LaunchDaemons/com.macmon.plist that restarts on crash. The daemon runs as the user who called sudo, not as root, and with FileVault on it starts after the disk is unlocked following a restart. If that user has the agent below, the daemon replaces it.
To start at login instead, without sudo:
macmon serve --install --agent # install and start (takes --host and --port too)
macmon serve --uninstall # stop and removeThis creates a LaunchAgent at ~/Library/LaunchAgents/com.macmon.plist. Only one macmon service at a time: it's refused while the daemon is installed.
The /metrics endpoint exposes metrics in Prometheus format. See examples/grafana for a local demo stack with Prometheus and Grafana.
Prometheus output example
# HELP macmon_cpu_temp_celsius Average CPU temperature in Celsius
# TYPE macmon_cpu_temp_celsius gauge
macmon_cpu_temp_celsius{chip="Apple M3 Pro"} 47.3
# HELP macmon_cpu_power_watts CPU power consumption in Watts
# TYPE macmon_cpu_power_watts gauge
macmon_cpu_power_watts{chip="Apple M3 Pro"} 8.42
# HELP macmon_fan_speed_rpm Fan speed in revolutions per minute
# TYPE macmon_fan_speed_rpm gauge
macmon_fan_speed_rpm{chip="Apple M3 Pro",fan="fan0"} 1234
# HELP macmon_cpu_scaled_ratio Combined frequency-scaled CPU ratio (0β1), weighted by core count
# TYPE macmon_cpu_scaled_ratio gauge
macmon_cpu_scaled_ratio{chip="Apple M3 Pro"} 0.037
# HELP macmon_cpu_active_ratio Combined CPU active residency ratio (not frequency-scaled, 0β1), weighted by core count
# TYPE macmon_cpu_active_ratio gauge
macmon_cpu_active_ratio{chip="Apple M3 Pro"} 0.092
# HELP macmon_cpu_tier_freq_mhz CPU tier frequency in MHz
# TYPE macmon_cpu_tier_freq_mhz gauge
macmon_cpu_tier_freq_mhz{chip="Apple M3 Pro",tier="E"} 1100
macmon_cpu_tier_freq_mhz{chip="Apple M3 Pro",tier="P"} 1800
# HELP macmon_cpu_tier_scaled_ratio CPU tier frequency-scaled ratio (0β1)
# TYPE macmon_cpu_tier_scaled_ratio gauge
macmon_cpu_tier_scaled_ratio{chip="Apple M3 Pro",tier="E"} 0.083
macmon_cpu_tier_scaled_ratio{chip="Apple M3 Pro",tier="P"} 0.015
# HELP macmon_cpu_tier_active_ratio CPU tier active residency ratio (not frequency-scaled, 0β1)
# TYPE macmon_cpu_tier_active_ratio gauge
macmon_cpu_tier_active_ratio{chip="Apple M3 Pro",tier="E"} 0.18
macmon_cpu_tier_active_ratio{chip="Apple M3 Pro",tier="P"} 0.04
# HELP macmon_gpu_memory_alloc_bytes System memory allocated by the GPU in bytes
# TYPE macmon_gpu_memory_alloc_bytes gauge
macmon_gpu_memory_alloc_bytes{chip="Apple M3 Pro"} 2902786048
# HELP macmon_gpu_memory_in_use_bytes System memory in use by the GPU in bytes
# TYPE macmon_gpu_memory_in_use_bytes gauge
macmon_gpu_memory_in_use_bytes{chip="Apple M3 Pro"} 1056669696
macmon_ecpu_* and macmon_pcpu_* are still exported for the lowest and the highest tier. In a macOS VM the power and frequency gauges are left out.
Use macmon stress to generate load while checking metric behavior:
macmon stress
macmon stress pulse --duration 30
macmon stress cpu --duration 30
macmon stress cpu --workers 8 --duration 30
macmon stress gpu --duration 30
macmon stress all --duration 30The default pulse mode generates a predictable cyclic CPU load with a fixed 50% duty cycle on half of the logical CPUs. The cpu and gpu modes continuously load only the selected processor, while all loads both. The cpu and all modes use all logical CPUs unless --workers is specified; --workers has no effect in gpu mode.
macOS 27 broke the CPU/ANE power source that macmon previously used without root. The IOReport Energy Model counters can remain visible but stop updating: refreshing them now requires the private com.apple.private.pmgr.nrg.reporting entitlement. Apple's signed powermetrics has this entitlement and can keep using the named channels. Running macmon with sudo does not grant the same access.
The working rootless workaround we found is to read hidden energy reports from the AppleCLPC driver through IOReport. These reports use opaque IDs instead of discoverable CPU/GPU/ANE channel names, so macmon has to identify the counters and verify their units. This leaves macmon maintaining undocumented mappings for data that Apple's own tool can still read directly.
chip_power is the power of the whole chip: its own total where it reports one, and never less than the sum of CPU, GPU, ANE, memory (ram_power), memory controller (dcs_power) and media power. media_power, the video encoder and image processor, is reported apart only on M6; on other chips it is part of chip_power. all_power is the same as chip_power, kept for compatibility.
Both the CLI and the Rust library use known CLPC counters automatically, and Sampler::new() retains legacy fallbacks for older systems. New chips or driver versions may need additional mappings. Until then macmon estimates CPU power from rougher per-cluster statistics where the chip has them (M5; close under load, up to about 1.5 W high at light load) and memory power from the SMC (M5). On an unsupported configuration without them, zero CPU/ANE readings can mean unavailable counters rather than zero consumption. See docs/clpc-discovery.md for details.
On a Mac without a battery, system power (sys_power) comes from the power supply when the usual sensor reads 0, as on Mac Studio.
The pipe command and the HTTP /json endpoint return the same metrics. Floats are rounded to six decimal places. Network, disk, uptime and battery are data only: there, in Prometheus and in macmon debug, not in the TUI. In a macOS VM ("vm": true in the soc object of pipe --soc-info and /json) power and frequencies read 0, temperatures null and fans are empty:
JSON example
dc_in_power is the power coming in from the power adapter, about 0 on battery. display_power is the power of the built-in display's backlight. Both are data only, not in the TUI, and null on Macs that don't report them.
active_ratio is the share of the sampling interval during which the processor was doing any work. scaled_ratio is the same measure adjusted for operating frequency, showing the share of the processor's maximum possible capacity that was used.
For interval
CPU ratios are calculated per core and averaged across the cluster. A core that did no work contributes zero; combined CPU ratios average all cores from all clusters:
Examples:
- Full interval at half the maximum frequency β
active = 1.0,scaled = 0.5. - Half the interval at the maximum frequency β
active = 0.5,scaled = 0.5. - One of ten cores working at maximum frequency β cluster
active = scaled = 0.1.
macmon can be used as a Rust library to collect Apple Silicon metrics in your own applications.
Add it to your project:
cargo add macmon --no-default-featuresThe default app feature enables the macmon executable and its terminal UI dependencies. Disable default features when using macmon only as a library.
Run the demo example from this repository:
cargo run --example demoThen use the Sampler to collect metrics:
use macmon::Sampler;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut sampler = Sampler::new()?;
// collect metrics over a 1000ms window
let metrics = sampler.get_metrics(1000)?;
println!("CPU power: {:.2} W", metrics.cpu_power);
println!("GPU power: {:.2} W", metrics.gpu_power);
match metrics.temp.cpu_temp_avg {
Some(value) => println!("CPU temp: {value:.1} Β°C"),
None => println!("CPU temp: N/A"),
}
println!("RAM usage: {} / {} bytes", metrics.memory.ram_usage, metrics.memory.ram_total);
for tier in &metrics.cpu_tiers {
println!("{}-CPU: {} MHz {:.1}%", tier.label, tier.freq_mhz, tier.scaled_ratio * 100.0);
}
Ok(())
}If your application already has a timer, create the sampler before the first tick and reuse it:
// Call this on each tick of your application's timer.
let metrics = sampler.get_metrics_now()?;get_metrics_now() reads the hardware immediately. It calculates counter-based metrics over the time since the previous sample taken by either method, or since sampler initialization on the first call.
get_metrics(duration_ms) blocks the calling thread while collecting one IOReport delta over the complete interval. For a UI, server, or async application, create the sampler inside a dedicated worker thread and send the completed metrics back through a channel:
use std::{sync::mpsc, thread};
use macmon::Sampler;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let mut sampler = Sampler::new().expect("failed to create sampler");
while let Ok(metrics) = sampler.get_metrics(1000) {
if tx.send(metrics).is_err() {
break;
}
}
});
// Use recv() in a consumer thread or try_recv() in a non-blocking event loop.
let metrics = rx.recv()?;
println!("CPU power: {:.2} W", metrics.cpu_power);
Ok(())
}Creating Sampler inside the worker keeps its low-level macOS handles on that thread. In an async runtime, use its blocking-thread facility rather than calling get_metrics directly from an executor worker.
htop and btop are good general monitors, and htop is lighter. On Apple Silicon, without sudo, macmon shows more:
- Real CPU and memory of other users' processes, such as
WindowServerand root services. The kernel refuses them to htop and btop, which show 0; macmon reads them through the system'sps. - CPU, ANE and total power, frequencies of each CPU cluster, and memory pressure.
- Power and GPU use per process.
- A process filter with queries (
mem:>1g,app:safari), open files and sockets of a process.
Compared with htop 3.5.3 and btop 1.4.8 on macOS 27.
- tlkh/asitop β The original tool. Written in Python, requires sudo.
- dehydratedpotato/socpowerbud β Written in Objective-C, sudoless, no TUI.
- op06072/NeoAsitop β Written in Swift, sudoless.
- graelo/pumas β Written in Rust, requires sudo.
- context-labs/mactop β Written in Go, requires sudo.
Pull requests are welcome, including ones made with agents: point yours at agents.md.
Distributed under the MIT License.

{ "timestamp": "2025-02-24T20:38:15.427569+00:00", "temp": { "cpu_temp_avg": 43.73614, // Celsius, null when no sensor has a valid reading "gpu_temp_avg": 36.95167, // Celsius, null when no sensor has a valid reading }, "memory": { "ram_total": 25769803776, // Bytes "ram_usage": 20985479168, // Bytes "swap_total": 4294967296, // Bytes "swap_usage": 2602434560, // Bytes "pressure_level": 0, // Kernel memory pressure level: 0 normal, 1 warning, 2 critical, null when unavailable "pressure_percent": 25, // Memory pressure as in Activity Monitor (0β100), null when unavailable }, "fans": [ { "name": "fan0", "rpm": 999, "max_rpm": 4900 }, { "name": "fan1", "rpm": 1200, "max_rpm": 5200 }, ], "cpu_scaled_ratio": 0.036854, // Combined frequency-scaled CPU ratio (weighted by core count, 0β1) "cpu_active_ratio": 0.092, // Combined active residency ratio (not frequency-scaled, weighted by core count, 0β1) "cpu_tiers": [ // CPU core types from the lowest to the highest: E, P on M1βM4; E, S on M5; P, S on M5 Pro/Max; E, P, S on M6; _ when the core type is unknown, e.g. the single tier of a macOS VM { "label": "E", "freq_mhz": 1100, // Tier frequency "scaled_ratio": 0.082657, // Frequency-scaled ratio (0β1) "active_ratio": 0.18, // Active residency (not frequency-scaled, 0β1) "cores": [ { "die_id": 0, "core_id": 0, "freq_mhz": 1600, "scaled_ratio": 0.14, "active_ratio": 0.24 }, { "die_id": 0, "core_id": 1, "freq_mhz": 1700, "scaled_ratio": 0.12, "active_ratio": 0.2 }, ], }, { "label": "P", "freq_mhz": 1800, "scaled_ratio": 0.015182, "active_ratio": 0.04, "cores": [ { "die_id": 0, "core_id": 0, "freq_mhz": 2100, "scaled_ratio": 0.05, "active_ratio": 0.08 }, { "die_id": 0, "core_id": 1, "freq_mhz": 2200, "scaled_ratio": 0.07, "active_ratio": 0.06 }, ], }, ], // Deprecated, use cpu_tiers: ecpu_* is the lowest tier, pcpu_* the highest "ecpu_freq_mhz": 1100, "ecpu_scaled_ratio": 0.082657, "ecpu_active_ratio": 0.18, "pcpu_freq_mhz": 1800, "pcpu_scaled_ratio": 0.015182, "pcpu_active_ratio": 0.04, "ecpu_cores": [/* cores of the first tier */], "pcpu_cores": [/* cores of the last tier */], "gpu_freq_mhz": 461, // GPU frequency "gpu_scaled_ratio": 0.021498, // Frequency-scaled ratio (0β1) "gpu_active_ratio": 0.09, // GPU active residency ratio (not frequency-scaled, 0β1) "cpu_power": 0.204864, // Watts "gpu_power": 0.017452, // Watts "ane_power": 0.0, // Watts "chip_power": 1.413055, // Whole chip, Watts "all_power": 1.413055, // Deprecated, use chip_power "sys_power": 5.876533, // Watts "ram_power": 0.116358, // Memory (DRAM), Watts "dcs_power": 0.081194, // Memory controller, Watts, null without a source "media_power": null, // Video encoder and image processor, Watts, null except on M6 "gpu_ram_power": 0.000962, // Deprecated, GPU SRAM power, Watts "dc_in_power": 11.56, // Power adapter input, Watts, null without a source "display_power": null, // Built-in display backlight, Watts, null without a source "net_in": 148210, // Bytes per second received by the Ethernet and Wi-Fi interfaces, null without one "net_out": 20544, // Bytes per second sent by the Ethernet and Wi-Fi interfaces, null without one "disk_read": 1048576, // Bytes per second read from the disks, without disk images, null when unavailable "disk_write": 262144, // Bytes per second written to the disks, without disk images, null when unavailable "uptime": 273600, // Seconds since boot, null when unavailable "battery_percent": 87, // Battery charge (0β100), null without a battery "battery_charging": true, // null without a battery "battery_plugged": true, // External power connected, charging or not, null without a battery "gpu_mem_alloc": 2902786048, // Bytes of system memory allocated by the GPU, null when unavailable "gpu_mem_in_use": 1056669696, // Bytes of system memory in use by the GPU, null when unavailable }