Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

116 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

macmon – Mac Monitor

donate

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
  • 🎨 Switchable color themes (6 variants)
  • πŸͺŸ Can be displayed in a small window
  • πŸ¦€ Written in Rust

πŸ“₯ Installation

Install macmon using brew:

brew install macmon
Other installation methods

Install using MacPorts:

sudo port install macmon

Install using Cargo:

cargo install macmon

Install using Nix:

nix-env -i macmon

πŸš€ Usage

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 version

Interactive mode

Run macmon without a subcommand to open the terminal UI.

Controls:
  c - change color
  v - switch charts view: gauge / sparkline
  d - toggle detailed CPU/RAM view
  r - switch ratio mode: scaled / active
  q - quit

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:

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:

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

launchd service

To start macmon serve automatically on login and keep it running:

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 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_ecpu_scaled_ratio Efficiency CPU cluster frequency-scaled ratio (0–1)
# TYPE macmon_ecpu_scaled_ratio gauge
macmon_ecpu_scaled_ratio{chip="Apple M3 Pro"} 0.083

# HELP macmon_ecpu_freq_mhz Efficiency CPU cluster frequency in MHz
# TYPE macmon_ecpu_freq_mhz gauge
macmon_ecpu_freq_mhz{chip="Apple M3 Pro"} 1100

# HELP macmon_ecpu_active_ratio Efficiency CPU cluster active residency ratio (not frequency-scaled, 0–1)
# TYPE macmon_ecpu_active_ratio gauge
macmon_ecpu_active_ratio{chip="Apple M3 Pro"} 0.18

Stress testing

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

Output format

The pipe command and the HTTP /json endpoint return the same metrics:

JSON example
{
  "timestamp": "2025-02-24T20:38:15.427569+00:00",
  "temp": {
    "cpu_temp_avg": 43.73614, // Celsius
    "gpu_temp_avg": 36.95167, // Celsius
  },
  "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)
  "ecpu_freq_mhz": 1100, // Cluster frequency
  "ecpu_scaled_ratio": 0.082656614, // Frequency-scaled ratio (0–1)
  "ecpu_active_ratio": 0.18, // Active residency (not frequency-scaled, 0–1)
  "pcpu_freq_mhz": 1800, // Cluster frequency
  "pcpu_scaled_ratio": 0.015181795, // Frequency-scaled ratio (0–1)
  "pcpu_active_ratio": 0.04, // Active residency (not frequency-scaled, 0–1)
  "ecpu_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 },
  ],
  "pcpu_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 },
  ],
  "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:

$$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 both clusters:

$$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:

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 standalone demo app:

cargo run --manifest-path examples/demo-app/Cargo.toml

Then 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);
    println!("CPU temp:   {:.1} Β°C", metrics.temp.cpu_temp_avg);
    println!("RAM usage:  {} / {} bytes", metrics.memory.ram_usage, metrics.memory.ram_total);
    println!("eCPU:       {} MHz  {:.1}%", metrics.ecpu_freq_mhz, metrics.ecpu_scaled_ratio * 100.0);
    println!("pCPU:       {} MHz  {:.1}%", metrics.pcpu_freq_mhz, metrics.pcpu_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:

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.

🀝 Contributing

All contributions are welcome! Feel free to open an issue or submit a pull request.

πŸ“ License

Distributed under the MIT License.

πŸ” See also

About

πŸ¦€πŸŒ‘οΈ Real-time system monitor for Apple Silicon Macs (M1–M5). No sudo. TUI, JSON/Prometheus metrics server, and Rust library.

Topics

Resources

Stars

Watchers

Forks

Releases

Used by

Contributors

Languages