← back to Exo
unvendored macmon
40efed443684735ef5cf70eea65aa45b34108f4b · 2025-08-20 13:04:46 +0100 · Evan Quiney
Files touched
A .flake-modules/macmon.nixM flake.nixD worker/utils/macmon/bin/LICENSE.txtD worker/utils/macmon/bin/readme.mdM worker/utils/macmon/macmon.py
Diff
commit 40efed443684735ef5cf70eea65aa45b34108f4b
Author: Evan Quiney <evanev7@gmail.com>
Date: Wed Aug 20 13:04:46 2025 +0100
unvendored macmon
---
.flake-modules/macmon.nix | 30 +++++++
flake.nix | 1 +
worker/utils/macmon/bin/LICENSE.txt | 21 -----
worker/utils/macmon/bin/readme.md | 154 ------------------------------------
worker/utils/macmon/macmon.py | 40 +++-------
5 files changed, 40 insertions(+), 206 deletions(-)
diff --git a/.flake-modules/macmon.nix b/.flake-modules/macmon.nix
new file mode 100644
index 00000000..5df0cdf4
--- /dev/null
+++ b/.flake-modules/macmon.nix
@@ -0,0 +1,30 @@
+# Provides macmon binary for the worker.
+
+# These values would bind to the consumer flake when this flake module is imported:
+{
+ config,
+ self,
+ inputs,
+ getSystem,
+ moduleWithSystem,
+ withSystem,
+ ...
+}:
+
+# The actual flake-parts module configuration
+{
+ perSystem =
+ {
+ config,
+ self',
+ inputs',
+ pkgs,
+ system,
+ ...
+ }:
+ {
+ make-shells.default = {
+ packages = if (system == "aarch64-darwin") then ([ pkgs.macmon ]) else ([]);
+ };
+ };
+}
diff --git a/flake.nix b/flake.nix
index ce7d82d1..fd3f2c31 100644
--- a/flake.nix
+++ b/flake.nix
@@ -62,6 +62,7 @@
flakeModules.flakeRoot
flakeModules.justFlake
flakeModules.goForwarder
+ ./.flake-modules/macmon.nix
];
systems = [
"x86_64-linux"
diff --git a/worker/utils/macmon/bin/LICENSE.txt b/worker/utils/macmon/bin/LICENSE.txt
deleted file mode 100644
index 4659b63d..00000000
--- a/worker/utils/macmon/bin/LICENSE.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2024 vladkens
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/worker/utils/macmon/bin/readme.md b/worker/utils/macmon/bin/readme.md
deleted file mode 100644
index 9c44eed7..00000000
--- a/worker/utils/macmon/bin/readme.md
+++ /dev/null
@@ -1,154 +0,0 @@
-# `macmon` – Mac Monitor
-
-<div align="center">
-
-Sudoless performance monitoring CLI tool for Apple Silicon processors.
-
-[<img src="https://badges.ws/github/assets-dl/vladkens/macmon" />](https://github.com/vladkens/macmon/releases)
-[<img src="https://badges.ws/github/release/vladkens/macmon" />](https://github.com/vladkens/macmon/releases)
-[<img src="https://badges.ws/github/license/vladkens/macmon" />](https://github.com/vladkens/macmon/blob/main/LICENSE)
-[<img src="https://badges.ws/badge/-/buy%20me%20a%20coffee/ff813f?icon=buymeacoffee&label" alt="donate" />](https://buymeacoffee.com/vladkens)
-
-</div>
-
-<div align="center">
- <img src="https://github.com/vladkens/macmon/blob/assets/macmon.png?raw=true" alt="preview" />
-</div>
-
-## Motivation
-
-Apple Silicon processors don't provide an easy way to see live power consumption. I was interested in this information while testing local LLM models. `asitop` is a nice and simple TUI to quickly see current metrics, but it reads data from `powermetrics` and requires root privileges. `macmon` uses a private macOS API to gather metrics (essentially the same as `powermetrics`) but runs without sudo. 🎉
-
-## 🌟 Features
-
-- 🚫 Works without sudo
-- ⚡ Real-time CPU / GPU / ANE power usage
-- 📊 CPU utilization per cluster
-- 💾 RAM / Swap usage
-- 📈 Historical charts + avg / max values
-- 🌡️ Average CPU / GPU temperature
-- 🎨 Switchable colors (6 variants)
-- 🪟 Can be rendered in a small window
-- 🦀 Written in Rust
-
-## 🍺 Install via Homebrew
-
-You can install [`macmon`](https://formulae.brew.sh/formula/macmon) using [brew](https://brew.sh/):
-
-```sh
-$ brew install macmon
-```
-
-## 🖥️ Install via MacPorts
-
-You can also install [`macmon`](https://ports.macports.org/port/macmon/) using [MacPorts](https://macports.org/):
-
-```sh
-$ sudo port install macmon
-```
-
-## 📦 Install from source
-
-1. Install [Rust toolchain](https://www.rust-lang.org/tools/install)
-
-2. Clone the repo:
-
-```sh
-git clone https://github.com/vladkens/macmon.git && cd macmon
-```
-
-3. Build and run:
-
-```sh
-cargo run -r
-```
-
-4. (Optionally) Binary can be moved to bin folder:
-
-```sh
-sudo cp target/release/macmon /usr/local/bin
-```
-
-## 🚀 Usage
-
-```sh
-Usage: macmon [OPTIONS] [COMMAND]
-
-Commands:
- pipe Output metrics in JSON format
- debug Print debug information
- 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
- q - quit
-```
-
-## 🚰 Piping
-
-You can use the pipe subcommand to output metrics in JSON format, which is suitable for piping into other tools or scripts. For example:
-
-```sh
-macmon pipe | jq
-```
-
-This command runs `macmon` in "pipe" mode and navigate output to `jq` for pretty-printing.
-
-You can also specify the number of samples to run using `-s` or `--samples` parameter (default: `0`, which runs indefinitely), and set 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.
-
-### Output
-
-```jsonc
-{
- "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
- },
- "ecpu_usage": [1181, 0.082656614], // (Frequency MHz, Usage %)
- "pcpu_usage": [1974, 0.015181795], // (Frequency MHz, Usage %)
- "gpu_usage": [461, 0.021497859], // (Frequency MHz, Usage %)
- "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 // Watts (not sure what it means)
-}
-```
-
-## 🤝 Contributing
-We love contributions! Whether you have ideas, suggestions, or bug reports, feel free to open an issue or submit a pull request. Your input is essential in helping us improve `macmon` 💪
-
-## 📝 License
-`macmon` is distributed under the MIT License. For more details, check out the LICENSE.
-
-## 🔍 See also
-- [tlkh/asitop](https://github.com/tlkh/asitop) – Original tool. Python, requires sudo.
-- [dehydratedpotato/socpowerbud](https://github.com/dehydratedpotato/socpowerbud) – ObjectiveC, sudoless, no TUI.
-- [op06072/NeoAsitop](https://github.com/op06072/NeoAsitop) – Swift, sudoless.
-- [graelo/pumas](https://github.com/graelo/pumas) – Rust, requires sudo.
-- [context-labs/mactop](https://github.com/context-labs/mactop) – Go, requires sudo.
-
----
-
-*PS: One More Thing... Remember, monitoring your Mac's performance with `macmon` is like having a personal trainer for your processor — keeping those cores in shape! 💪*
diff --git a/worker/utils/macmon/macmon.py b/worker/utils/macmon/macmon.py
index 26b18416..8814fbd9 100644
--- a/worker/utils/macmon/macmon.py
+++ b/worker/utils/macmon/macmon.py
@@ -1,8 +1,7 @@
import asyncio
-import os
import platform
import subprocess
-from pathlib import Path
+import shutil
from typing import Optional, Tuple
from pydantic import BaseModel, ConfigDict, ValidationError
@@ -12,16 +11,10 @@ class MacMonError(Exception):
"""Exception raised for errors in the MacMon functions."""
-def _get_binary_path(binary_path: Optional[str] = None) -> str:
+def _get_binary_path() -> str:
"""
Get the path to the macmon binary.
- Args:
- binary_path: Optional path to the binary. If not provided, will use the bundled binary.
-
- Returns:
- The path to the macmon binary.
-
Raises:
MacMonError: If the binary doesn't exist or can't be made executable.
"""
@@ -34,23 +27,11 @@ def _get_binary_path(binary_path: Optional[str] = None) -> str:
):
raise MacMonError("MacMon only supports macOS with Apple Silicon (ARM) chips")
- if binary_path:
- path = binary_path
- else:
- # Get the directory where this module is located
- module_dir = Path(os.path.dirname(os.path.abspath(__file__)))
- path = str(module_dir / "bin" / "macmon")
- # Ensure the binary exists and is executable
- if not os.path.isfile(path):
- raise MacMonError(f"Binary not found at: {path}")
+ path = shutil.which("macmon")
- # Make the binary executable if it's not already
- if not os.access(path, os.X_OK):
- try:
- os.chmod(path, 0o755) # rwx r-x r-x
- except OSError as e:
- raise MacMonError(f"Failed to make binary executable: {e}") from e
+ if path is None:
+ raise MacMonError(f"MacMon not found in PATH")
return path
@@ -109,20 +90,17 @@ class Metrics(BaseModel):
# ---------------------------------------------------------------------------
-def get_metrics(binary_path: Optional[str] = None) -> Metrics:
+def get_metrics() -> Metrics:
"""
Run the binary and return the metrics as a Python dictionary.
- Args:
- binary_path: Optional path to the binary. If not provided, will use the bundled binary.
-
Returns:
A mapping containing system metrics.
Raises:
MacMonError: If there's an error running the binary.
"""
- path = _get_binary_path(binary_path)
+ path = _get_binary_path()
try:
# Run the binary with the argument -s 1 and capture its output
@@ -138,7 +116,7 @@ def get_metrics(binary_path: Optional[str] = None) -> Metrics:
raise MacMonError(f"Error parsing JSON output: {e}") from e
-async def get_metrics_async(binary_path: Optional[str] = None) -> Metrics:
+async def get_metrics_async() -> Metrics:
"""
Asynchronously run the binary and return the metrics as a Python dictionary.
@@ -151,7 +129,7 @@ async def get_metrics_async(binary_path: Optional[str] = None) -> Metrics:
Raises:
MacMonError: If there's an error running the binary.
"""
- path = _get_binary_path(binary_path)
+ path = _get_binary_path()
try:
proc = await asyncio.create_subprocess_exec(
← ea9e5734 Refactor runner supervisor
·
back to Exo
·
feat: build system and homebrew compatibility be6f5ae7 →