meine
meine π - A CLI file manager and system utility built with Textual. It combines intuitive command parsing with rich tβ¦
π¦π‘οΈ Real-time system monitor for Apple Silicon Macs (M1βM5). No sudo. TUI, JSON/Prometheus metrics server, and Rust lβ¦
macmon β Mac Monitormacmon 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.
brew install macmon
Install using MacPorts:
sudo port install macmon
Install using Cargo:
cargo install macmon
Install using Nix:
nix-env -i macmon
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 Controls: c - change color v - switch charts view: gauge / sparkline d - toggle detailed CPU/RAM view r - switch ratio mode: scaled / active q - quit
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
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.
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.
{
"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
}
Both ratios are calculated from the same frequency-state residency counters, but answer different questions.
active_ratio is the fraction of the sampling interval spent in any active frequency state. Every active state counts equally, whether the device runs at its minimum or maximum frequency.
active_ratio = Ξ£ active_state_time / total_time
scaled_ratio weights each active state by its frequency relative to the hardware maximum. It represents the used fraction of the maximum possible frequency-time over the interval.
scaled_ratio = Ξ£(active_state_time Γ state_frequency / max_frequency) / total_time
A device active for the entire interval at half its maximum frequency reports active_ratio = 100% and scaled_ratio = 50%. The same scaled_ratio = 50% is produced by a device active for half the interval at maximum frequency, but its active_ratio is only 50%.
Cluster ratios are arithmetic means across all physical cores in the cluster. Consequently, one fully active core in a ten-core cluster contributes 10% to the cluster active_ratio. The combined CPU ratios are weighted by the core count of each cluster.
ecpu_freq_mhz and pcpu_freq_mhz are arithmetic means of per-core frequencies with the hardware minimum-frequency floor. gpu_freq_mhz is averaged over active residency.
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 |
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.
Add a scrape target to your prometheus.yml:
scrape_configs:
- job_name: macmon
static_configs:
- targets: ["localhost:9090"]
For a ready-to-run local example with Prometheus + Grafana, see example-grafana:
macmon serve --port 9090 cd example-grafana docker compose up -d
This example provisions:
http://localhost:9091http://localhost:9000Macmon Overview dashboardGrafana login:
macmonmacmonThen import or build a Grafana dashboard querying metrics such as:
macmon_cpu_power_watts{chip="Apple M3 Pro"}
macmon_ecpu_scaled_ratio{chip="Apple M3 Pro"}
macmon_ecpu_freq_mhz{chip="Apple M3 Pro"}
macmon_memory_ram_used_bytes{chip="Apple M3 Pro"}
# 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
Use macmon stress to generate load while checking metric behavior:
macmon stress macmon stress --duration 30 macmon stress --full --duration 30 macmon stress --full --workers 8 --duration 30
The default remains the predictable cyclic CPU load with a fixed 50% duty cycle and 4 CPU workers. Use --full for continuous CPU and GPU load; when --workers is omitted, full mode uses all logical CPUs.
All contributions are welcome! Feel free to open an issue or submit a pull request.
Distributed under the MIT License.
more like this
search projects, people, and tags