# `macmon` – Mac Monitor
[](https://github.com/vladkens/macmon/releases) [](https://github.com/vladkens/macmon/releases) [](https://github.com/vladkens/macmon/blob/main/LICENSE) [donate](https://buymeacoffee.com/vladkens)
`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.
preview
## 🌟 Features - 🚫 Runs without sudo - ⚡ Real-time CPU / GPU / ANE power usage - 📊 CPU frequency-scaled and active ratios per cluster - 💾 RAM / Swap usage - 📈 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 ## 📥 Installation Install [`macmon`](https://formulae.brew.sh/formula/macmon) using [brew](https://brew.sh/): ```sh brew install macmon ```
Other installation methods Install using [MacPorts](https://ports.macports.org/port/macmon/): ```sh sudo port install macmon ``` Install using [Cargo](https://crates.io/crates/macmon): ```sh cargo install macmon ``` Install using [Nix](https://search.nixos.org/packages?show=macmon&from=0&size=50&type=packages&query=macmon): ```sh nix-env -i macmon ```
## 🚀 Usage ```sh 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 Update interval in milliseconds [default: 1000] -h, --help Print help -V, --version Print version ``` ### Interactive mode Run `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. Press `/` to filter the process list. A filter is a comma-separated list of terms, each matched case-insensitively against the process name or pid: `safari, cargo` shows processes matching any term, and a term starting with `!` hides its matches (`chrome, !helper`). `Enter` keeps the filter, `Esc` clears it. ### JSON output 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: ```sh macmon pipe | jq ``` This 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: ```sh macmon pipe -s 10 -i 500 | jq ``` This will collect 10 samples with an update interval of 500 milliseconds. ### HTTP server You can use the `serve` subcommand to expose metrics over HTTP. This is useful for integrating with monitoring systems like [Prometheus](https://prometheus.io/) and [Grafana](https://grafana.com/). ```sh 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 background ``` Two 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](https://prometheus.io/docs/instrumenting/exposition_formats/) | #### launchd service To start `macmon serve` automatically on login and keep it running: ```sh macmon serve --install # install and start (default port 9090) macmon serve --port 8080 --install # with custom port macmon serve --host 127.0.0.1 --install # listen on localhost only macmon serve --uninstall # stop and remove ``` This creates a launchd agent at `~/Library/LaunchAgents/com.macmon.plist` that auto-starts on login and restarts on crash. #### Prometheus and Grafana The `/metrics` endpoint exposes metrics in Prometheus format. See [`examples/grafana`](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 ``` `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.
### Stress testing Use `macmon stress` to generate load while checking metric behavior: ```sh 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 30 ``` The 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. ## 📊 Metrics ### Power sources and macOS 27 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. 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. On an unsupported configuration, zero CPU/ANE readings can mean unavailable counters rather than zero consumption. See [docs/clpc-discovery.md](docs/clpc-discovery.md) for details. ### Output format The `pipe` command and the HTTP `/json` endpoint return the same metrics. 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 ```jsonc { "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 }, "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.082656614, // 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.015181795, "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.082656614, "ecpu_active_ratio": 0.18, "pcpu_freq_mhz": 1800, "pcpu_scaled_ratio": 0.015181795, "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.021497859, // Frequency-scaled ratio (0–1) "gpu_active_ratio": 0.09, // GPU active residency ratio (not frequency-scaled, 0–1) "cpu_power": 0.20486385, // Watts "gpu_power": 0.017451683, // Watts "ane_power": 0.0, // Watts "all_power": 0.22231553, // Watts "sys_power": 5.876533, // Watts "ram_power": 0.11635789, // Watts "gpu_ram_power": 0.0009615385, // GPU SRAM power, Watts } ```
### Active and scaled ratios `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 $T$, $t_i$ is the time spent doing work at frequency $f_i$, and $f_\text{max}$ is the hardware maximum frequency for the CPU cluster or GPU. The product $T f_\text{max}$ is the theoretical maximum frequency-time the processor could deliver at full load without thermal or power throttling: ```math R_\text{active} = \frac{\sum_i t_i}{T} \qquad R_\text{scaled} = \frac{\sum_i t_i f_i}{T f_\text{max}} ``` 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: ```math R_\text{cluster} = \frac{1}{N_\text{cores}}\sum_j R_j ``` 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`. ## 📚 Library usage `macmon` can be used as a Rust library to collect Apple Silicon metrics in your own applications. Add it to your project: ```sh cargo add macmon --no-default-features ``` The 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](examples/demo.rs) from this repository: ```sh cargo run --example demo ``` Then use the `Sampler` to collect metrics: ```rust use macmon::Sampler; fn main() -> Result<(), Box> { 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(()) } ``` `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: ```rust use std::{sync::mpsc, thread}; use macmon::Sampler; fn main() -> Result<(), Box> { 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. ## 🤝 Contributing All contributions are welcome! Feel free to open an issue or submit a pull request. The [dev pre-release](https://github.com/vladkens/macmon/releases/tag/dev), built from `main`, shows its git version in `macmon -V` and the TUI title, e.g. `macmon 0.9.0-12-g3672a1b` (local builds show the crate version); mention it when reporting an issue. ## 📝 License Distributed under the [MIT License](LICENSE). ## 🔍 See also - [tlkh/asitop](https://github.com/tlkh/asitop) – The original tool. Written in Python, requires sudo. - [dehydratedpotato/socpowerbud](https://github.com/dehydratedpotato/socpowerbud) – Written in Objective-C, sudoless, no TUI. - [op06072/NeoAsitop](https://github.com/op06072/NeoAsitop) – Written in Swift, sudoless. - [graelo/pumas](https://github.com/graelo/pumas) – Written in Rust, requires sudo. - [context-labs/mactop](https://github.com/context-labs/mactop) – Written in Go, requires sudo.