[object Object]

← back to Exo

unvendored macmon

40efed443684735ef5cf70eea65aa45b34108f4b · 2025-08-20 13:04:46 +0100 · Evan Quiney

Files touched

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 →