unvendored macmon

This commit is contained in:
Evan Quiney
2025-08-20 13:04:46 +01:00
committed by GitHub
parent ea9e573409
commit 40efed4436
5 changed files with 40 additions and 206 deletions
+30
View File
@@ -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 ([]);
};
};
}
+1
View File
@@ -62,6 +62,7 @@
flakeModules.flakeRoot
flakeModules.justFlake
flakeModules.goForwarder
./.flake-modules/macmon.nix
];
systems = [
"x86_64-linux"
-21
View File
@@ -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.
-154
View File
@@ -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! 💪*
+9 -31
View File
@@ -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(