{"id":51777429,"url":"https://github.com/binlecode/actop","last_synced_at":"2026-07-20T07:31:01.033Z","repository":{"id":338709392,"uuid":"1158811852","full_name":"binlecode/actop","owner":"binlecode","description":"Apple Silicon (M1–M4) power, GPU, ANE \u0026 memory-bandwidth monitor — sudoless TUI + Python API for profiling local LLM / MLX / CoreML inference","archived":false,"fork":false,"pushed_at":"2026-07-01T00:10:44.000Z","size":3540,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-01T00:14:53.100Z","etag":null,"topics":["ane","apple-silicon","gpu-monitoring","llm","macos","mlx","ollama","performance-monitoring","python","tui"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/binlecode.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-16T00:03:36.000Z","updated_at":"2026-07-01T00:11:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/binlecode/actop","commit_stats":null,"previous_names":["binlecode/agtop","binlecode/actop"],"tags_count":54,"template":false,"template_full_name":null,"purl":"pkg:github/binlecode/actop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binlecode%2Factop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binlecode%2Factop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binlecode%2Factop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binlecode%2Factop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/binlecode","download_url":"https://codeload.github.com/binlecode/actop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binlecode%2Factop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35678463,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"ssl_error","status_checked_at":"2026-07-20T02:08:09.736Z","response_time":111,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ane","apple-silicon","gpu-monitoring","llm","macos","mlx","ollama","performance-monitoring","python","tui"],"created_at":"2026-07-20T07:31:00.390Z","updated_at":"2026-07-20T07:31:01.027Z","avatar_url":"https://github.com/binlecode.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# actop\n\n**Watch your Apple Silicon Mac the way it actually works — and profile your own workloads from Python.**\n\n`actop` is a sudoless, in-process performance monitor for M1–M4 Macs: a real-time\nTUI for CPU/GPU/ANE utilization, per-core frequency, memory **bandwidth**, power,\nand thermals — plus a **Python API** (`Monitor` / `Profiler`, `to_pandas()`) so you\ncan instrument your *own* local LLM / MLX / CoreML inference and training runs with\nSoC-accurate power and energy context.\n\n![actop live during an Ollama inference run: the single-column stack layout switching chart glyphs and revealing the process panel, with live E-CPU/P-CPU/GPU/ANE utilization, per-core frequency, memory bandwidth, power charts, and a watt-attributed PWR column on Apple Silicon](images/actop-demo.gif)\n\n*Above: actop live under a local LLM run — the `stack` layout with braille then block sparklines, then the process panel (`t`) with its watt-attributed `PWR` column. Below: the default two-column `grid` layout (`l` cycles between them).*\n\n![actop dashboard in the default two-column grid layout: E-CPU/P-CPU/GPU/ANE utilization, per-core frequency, memory bandwidth, and power charts on Apple Silicon](images/actop.png)\n\n**Who it's for**\n\n- **Running LLMs locally** (MLX, llama.cpp, Ollama) and want to see whether you're\n  GPU-bound, memory-**bandwidth**-bound, or leaving the ANE idle — at a glance.\n- **Profiling your own code**: wrap a workload in `Monitor`/`Profiler`, get a\n  pandas frame of power, frequency, residency, and cumulative session energy.\n- **Just want a clean `*top`** for Apple Silicon that needs **no `sudo`**.\n\nInstall in one line — [Homebrew](#homebrew-recommended) or [uv](#uv-recommended-for-non-homebrew-users) — then run `actop`.\n\n## Background\n\n`actop` is an independent project with its own idea, architecture, codebase, and release cycle. It was inspired by `tlkh/asitop` — built to fill the gaps that tool left for whole-chip, sudoless, programmable monitoring. The name carries the Unix `*top` lineage: `actop` (*Apple **C**hip top*) follows `asitop` (*Apple Silicon top*), and covers the whole SoC — CPU, GPU, ANE, power, memory bandwidth, and thermal.\n\nThe original `asitop` shells out to Apple's `powermetrics` CLI, a high-level tool that requires `sudo`, writes to temp files, and returns pre-aggregated metrics at a fixed cadence. `actop` instead calls the underlying IOReport C library directly via Python ctypes — the same library that `powermetrics` itself uses internally. This low-level approach runs unprivileged, avoids subprocess and file I/O overhead, gives access to raw per-core residency states and energy counters, and lets the application control its own sampling interval and delta computation.\n\n**Why another `*top`?** `mactop` (Go) and `macmon` (Rust) are excellent sudoless TUIs — mactop the broadest in features, macmon the leanest binary. `actop`'s reason to exist is different: it is the **programmable, Python-native** one. A public API (`Monitor` / `Profiler`, `to_pandas()`) lets you instrument and profile your *own* workloads — local LLM / CoreML / MLX inference, training loops — from Python, with SoC-accurate power context and cumulative session energy. It's the data scientist's profiler, not just a dashboard — and unlike the `sudo`-bound `asitop` that inspired it, it needs no `sudo`. See [Where actop fits](#where-actop-fits) for the head-to-head.\n\n## Key Features\n\n- **Textual TUI dashboard**: custom `BrailleChart` sparklines for E-CPU, P-CPU, GPU, ANE, RAM, and power — rendered on the [Textual](https://textual.textualize.io/) framework. Four titled sections (CPU, GPU·ANE, Memory, Power) in two switchable layout presets — `grid` (two columns, fits short terminals) and `stack` (single full-width column), cycled live with `l`. Supports `dots` (braille) and `block` glyph styles, and selectable color palettes (`--palette`: `thermal`, colorblind-safe `viridis`, grayscale `mono`) on top of adaptive truecolor/256/16-color/`NO_COLOR` degradation. Resizes cleanly; no raw ANSI escape sequences.\n- **In-process IOReport sampling**: reads Apple Silicon power, frequency, and residency metrics via Python ctypes bindings to `libIOReport.dylib` and CoreFoundation. No subprocesses, no temp files.\n- **Per-core visibility**: per-core panels on by default; toggle with `--no-show_cores` for a cluster-level view.\n- **Diagnosis-oriented alerts**: configurable sustained-sample thresholds for thermal pressure, bandwidth saturation, swap growth, and package power. Active alerts are shown inline in the status line.\n- **Process monitoring (optional)**: top CPU/RSS processes panel is off by default. Enable at launch with `--show-processes` or press `t` in the TUI. Regex filtering is available via `--proc-filter` or `/` interactively.\n- **Profile-aware power scaling**: `profile` mode (default) scales charts against the SoC's known reference wattage for stable cross-session comparison; `auto` mode scales against rolling peak.\n- **SoC compatibility**: 16 built-in M1–M4 profiles (base, Pro, Max, Ultra). Unknown future chips fall back to tier-based defaults using the latest generation's reference values.\n- **CPU/GPU temperature**: reads die temperatures from the Apple SMC (System Management Controller) via IOKit ctypes. Displayed inline in gauge titles (e.g. \"P-CPU Usage: 12% @ 3504 MHz (58°C)\"). No sudo required.\n\n## Where actop fits\n\nHow the sudoless, in-process field stacks up:\n\n| | actop | [mactop](https://github.com/metaspartan/mactop) | [macmon](https://github.com/vladkens/macmon) |\n|---|:---:|:---:|:---:|\n| Unprivileged, in-process (no sudo) | ✅ | ✅ | ✅ |\n| CPU/GPU/ANE power · temps · bandwidth | ✅ | ✅ | ✅ |\n| Per-process power/energy attribution (`PWR` column) | ✅ | — | — |\n| Bandwidth % of SoC peak + `MEM-BOUND` saturation alert | ✅ | — | — |\n| Throttle-state indicator (`THROTTLING:CPU/GPU`) | ✅ | — | — |\n| DVFS P-state residency distribution (per-cluster) | ✅ | — | — |\n| Python API (`Monitor`/`Profiler`, `to_pandas()`) | ✅ | — | — |\n| SoC-accurate power scaling (M1–M4 profiles) | ✅ | rolling peak | rolling peak |\n| Session energy (∫ package power) | ✅ | — | — |\n| Net/disk I/O · fan RPM · menu bar | — | ✅ | fan only |\n\nFor the broadest TUI and DevOps feature set (network/disk I/O, a menu-bar app, more export formats), use **mactop**; for the leanest single Rust binary, **macmon**. Full head-to-head: [docs/REVIEW-architecture-comparison.md](docs/REVIEW-architecture-comparison.md).\n\n## Roadmap\n\nThe gaps above are tracked, not abandoned — see\n[docs/TODO-architecture-roadmap.md](docs/TODO-architecture-roadmap.md) for the open\nitems and their reasoning:\n\n- **Net/disk I/O** — the one open must-have for hardware/metric coverage; the\n  feasibility spike is done (verified on-device, unprivileged) and the full\n  implementation-ready design lives in `docs/TODO-net-disk-io-2026-07-02.md`.\n- **Menu bar mode** — deliberately deferred post-launch; a second application\n  surface (not a feature add), revisited only after the initial launch cycle.\n\nactop stays scoped to one thesis — a fast, unprivileged, resource-efficient Apple\nSilicon telemetry monitor — and rejects feature creep into general ML/APM frameworks.\n\n## Installation\n\n### Homebrew (recommended)\n\n```shell\nbrew tap binlecode/actop\nbrew install binlecode/actop/actop\n```\n\nThe tap name `binlecode/actop` resolves to the formula repo\n[`binlecode/homebrew-actop`](https://github.com/binlecode/homebrew-actop) (Homebrew's\n`user/repo` → `github.com/user/homebrew-repo` convention). The formula is\nself-contained: it depends on Homebrew's `python@3.13` and installs `actop` into its\nown isolated `libexec` virtualenv — it does **not** use (or interfere with) the macOS\nsystem Python.\n\nUpgrade / uninstall:\n\n```shell\nbrew upgrade binlecode/actop/actop\nbrew uninstall binlecode/actop/actop \u0026\u0026 brew untap binlecode/actop\n```\n\n### uv (recommended for non-Homebrew users)\n\n[`uv`](https://docs.astral.sh/uv/) installs `actop` into a sandboxed, per-tool\nenvironment with its own managed CPython — no system Python required and no\ninterpreter drift:\n\n```shell\nuv tool install git+https://github.com/binlecode/actop.git\n```\n\nUpgrade / uninstall:\n\n```shell\nuv tool upgrade actop\nuv tool uninstall actop\n```\n\n### pip\n\n```shell\npip install git+https://github.com/binlecode/actop.git\n```\n\n## Quick Start\n\n```shell\nactop                                               # full dashboard with per-core panels, profile power scaling\nactop --interval 1 --avg 10                        # faster refresh, shorter rolling window\nactop --show-processes                              # include top process panel at startup\nactop --proc-filter \"python|ollama|vllm|docker|mlx\"  # filter process panel at launch\nactop --no-show_cores                               # cluster-level view without per-core panels\nactop --chart-glyph block                           # square block chart glyphs\nactop --layout stack                                # single full-width scrolling column (default is grid)\nactop --json                                        # stream NDJSON metrics to stdout (no TUI)\nactop --serve 9095                                  # serve Prometheus metrics at :9095/metrics (no TUI)\n```\n\nInteractive keys: `p` pause · `s` cycle sort (CPU%→RSS→PID) · `g` toggle chart glyph (`dots`/`block`) · `l` cycle layout (`grid`⇄`stack`) · `t` toggle process panel · `/` filter processes · `?` help overlay · `q` quit\n\n## Python API\n\nWrap any workload to get a pandas frame of power/frequency/residency/energy — no TUI needed:\n\n```python\nfrom actop import Profiler\n\nwith Profiler() as p:\n    run_my_inference()\n\ndf = p.to_pandas()   # rows = samples; cols = power/freq/residency/energy\n```\n\n`to_pandas()` needs the `pandas` extra: `pip install \"actop[pandas]\"`. For a single point-in-time reading instead of a background collector, use `Monitor().get_snapshot()`.\n\nPer-process data is opt-in (off by default, so metrics-only callers pay no process-enumeration cost). Enable it to get the same watt-attributed processes the TUI shows:\n\n```python\nfrom actop import Monitor\n\nwith Monitor(include_processes=True) as m:\n    snap = m.get_snapshot()\n    for p in snap.processes:            # list[ProcessSample], CPU-sorted\n        print(p.pid, p.command, p.attributed_w)  # attributed_w is None until the first CPU delta\n```\n\n## CLI Reference\n\n| Option | Purpose | Default |\n| --- | --- | --- |\n| `--interval` | Sampling and refresh interval (seconds) | `2` |\n| `--avg` | Rolling average window (seconds) | `30` |\n| `--subsamples` | Internal sampler deltas per interval (≥1) | `1` |\n| `--show_cores` / `--no-show_cores` | Per-core panels | `on` |\n| `--show-processes` | Show top process panel at startup | `off` |\n| `--power-scale profile\\|auto` | Power chart scaling | `profile` |\n| `--chart-glyph dots\\|block` | Chart glyph style | `dots` |\n| `--palette thermal\\|viridis\\|mono` | Chart color palette: `thermal` (blue→red), `viridis` (colorblind-safe), `mono` (grayscale). Applies at truecolor/256 tiers | `thermal` |\n| `--layout grid\\|stack` | Dashboard layout preset (`grid` auto-degrades to `stack` under ~96 cols; cycle live with `l`) | `grid` |\n| `--proc-filter REGEX` | Filter process panel by command name | all (applies when panel is enabled) |\n| `--alert-bw-sat-percent` | Bandwidth saturation alert threshold | `85` |\n| `--alert-package-power-percent` | Package power alert threshold (profile-relative) | `85` |\n| `--alert-swap-rise-gb` | Swap growth alert threshold (GB) | `0.3` |\n| `--alert-sustain-samples` | Consecutive samples for sustained alerts | `3` |\n| `--json` | Stream metrics as NDJSON to stdout instead of the TUI | `off` |\n| `--serve PORT` | Serve Prometheus metrics on `http://0.0.0.0:PORT/metrics` instead of the TUI | `off` |\n\n## Metrics Export\n\nBeyond the interactive dashboard, actop can act as an observability source. Both\nmodes reuse the same unprivileged IOReport backend and exit on `Ctrl-C`.\n\n- **NDJSON stream** (`--json`): emits one compact JSON snapshot per `--interval`\n  to stdout — every `SystemSnapshot` field, including per-core lists. Pipe it to\n  `jq`, a log shipper, or a file:\n\n  ```shell\n  actop --json --interval 1 | jq '{cpu: .cpu_watts, pkg: .package_watts}'\n  ```\n\n- **Prometheus endpoint** (`--serve PORT`): exposes gauges at `/metrics`\n  (`actop_cpu_power_watts`, `actop_pcpu_utilization_percent`, per-core\n  `actop_core_utilization_percent{cluster,core}`, …). A background sampler keeps\n  the latest reading warm so scrapes return immediately:\n\n  ```shell\n  actop --serve 9095\n  curl -s localhost:9095/metrics\n  ```\n\n## How It Works\n\nactop accesses Apple Silicon hardware telemetry through three OS-level interfaces, all called in-process:\n\n### IOReport framework (`libIOReport.dylib`)\n\nThe primary data source. actop loads `libIOReport.dylib` and `CoreFoundation.framework` via `ctypes.cdll.LoadLibrary`, then:\n\n- Subscribes to three IOReport channel groups: **Energy Model** (CPU/GPU/ANE energy in nanojoules), **CPU Core Performance States** (per-core ECPU/PCPU DVFS residency), and **GPU Performance States** (GPU DVFS residency).\n- Takes periodic snapshots with `IOReportCreateSamples` and computes deltas between consecutive snapshots with `IOReportCreateSamplesDelta`.\n- Extracts per-channel energy values (`IOReportSimpleGetIntegerValue`) and per-state residency tables (`IOReportStateGetCount`, `IOReportStateGetNameForIndex`, `IOReportStateGetResidency`).\n- Converts raw items into a `SampleResult` — power (watts), frequency (MHz), and activity (percent). Energy values are converted from nanojoules to joules and scaled by elapsed time for correct wattage.\n\nAll CoreFoundation objects are managed via `CFRelease` to prevent memory leaks.\n\n### IOKit registry (`ioreg`)\n\nAt startup, reads `ioreg -a -r -d 1 -n pmgr` to get DVFS frequency tables from the power manager device node. Parses `voltage-states*` binary blobs as 8-byte `(freq_hz, voltage)` pairs and heuristically assigns tables to E-CPU, P-CPU, and GPU clusters. These translate opaque `V{n}P{m}` (CPU) and `P{n}` (GPU) state names into actual MHz values, computed as weighted averages across active P-states by residency time.\n\n### SMC (System Management Controller)\n\nReads CPU and GPU die temperatures via IOKit ctypes bindings to the `AppleSMC` kernel service. Discovers temperature sensor keys (Tp*/Te* for CPU, Tg* for GPU) at startup and reads `flt ` (IEEE 754 float) values each sample. Runs unprivileged.\n\n### Thermal pressure (`NSProcessInfo`)\n\nReads the macOS thermal state via the Objective-C runtime (`libobjc.A.dylib` + `Foundation.framework`) using ctypes. Calls `[NSProcessInfo processInfo].thermalState` each sample. The result is shown in the status line above the per-core history tracks:\n\n| State | Meaning |\n| --- | --- |\n| **Nominal** | Normal operating conditions — no throttling |\n| **Fair** | Mild thermal pressure — light throttling may begin |\n| **Serious** | Significant thermal pressure — noticeable throttling in effect |\n| **Critical** | Severe thermal pressure — aggressive throttling, system is very hot |\n\nNo sudo required. Degrades to `Unknown` if the ObjC runtime call fails.\n\n### System context\n\n- `sysctl`: SoC chip name, total/P/E core counts.\n- `system_profiler`: GPU core count.\n- Native `ctypes` (`native_sys.py`): RAM/swap usage (Mach VM stats, `XSWUsage`) and process enumeration (`proc_listpids`/`proc_pidinfo`) — no `psutil`.\n\n### Signal Sources\n\n| Signal | Source | Notes |\n| --- | --- | --- |\n| CPU/GPU/ANE power (W) | IOReport Energy Model | nJ per sample interval → watts |\n| Per-core frequency (MHz) | IOReport residency + DVFS tables | Weighted average of active P-states |\n| Per-core activity (%) | IOReport CPU Core Performance States | Via `CoreSample` (residency-weighted active%) |\n| GPU frequency and activity | IOReport GPU Performance States | Weighted average of GPUPH residencies |\n| CPU/GPU temperature (°C) | SMC via IOKit ctypes | Max die temp per cluster |\n| RAM / swap | Native `ctypes` (`host_statistics64` + `XSWUsage`) | `total - available` for used |\n| SoC profile | `sysctl` brand → 16 M1–M4 profiles | Tier fallbacks for unknown chips |\n| Top processes | Native `ctypes` (`proc_listpids`/`proc_pidinfo`) | Optional `--proc-filter` regex |\n| Bandwidth | IOReport (when available) | N/A if DCS counters not exposed |\n| Thermal pressure | `NSProcessInfo.thermalState` via ObjC runtime | Nominal / Fair / Serious / Critical |\n\n## Architecture\n\n| Module | Role |\n| --- | --- |\n| `actop/actop.py` | CLI entry point and argument parsing; thin wrapper launching the Textual TUI |\n| `actop/ioreport.py` | ctypes bindings to `libIOReport.dylib` and CoreFoundation — `IOReportSubscription` lifecycle, snapshot, delta, and CF helpers |\n| `actop/sampler.py` | `IOReportSampler`: two-snapshot delta logic, `SampleResult` conversion, DVFS table discovery from `ioreg pmgr`, SMC temperature integration |\n| `actop/smc.py` | SMC temperature reader: IOKit ctypes bindings to `AppleSMC`, key discovery, CPU/GPU die temperature reads |\n| `actop/utils.py` | L1 acquisition + platform discovery: native `ctypes` RAM/swap and per-process CPU/GPU time enumeration (via `native_sys.py`), `sysctl`/`system_profiler` SoC info |\n| `actop/analytics.py` | L2 domain analytics over acquired data points: per-process power attribution (`attribute_power`), throttle detection, and the `AlertEngine` (sustain-counted alerts + session-energy integral) |\n| `actop/soc_profiles.py` | 16 `SocProfile` dataclasses (M1–M4) with reference wattage/bandwidth; tier fallbacks for unknown chips |\n| `actop/power_scaling.py` | `power_to_percent()`: profile mode (SoC reference) vs auto mode (rolling peak x1.25) |\n| `actop/config.py` | `DashboardConfig` frozen dataclass; `create_dashboard_config()` merges CLI args with SoC info |\n| `actop/models.py` | `SystemSnapshot`, `CoreSample`, `ProcessSample` dataclasses (public API types) |\n| `actop/api.py` | `Monitor`, `Profiler`, `AsyncMonitor` — public Python API; `Monitor(include_processes=True)` opts into watt-attributed `ProcessSample`s |\n| `actop/tui/app.py` | `ActopApp`: Textual `App` with polling worker, process table, app-level status bar, interactive sort/filter/pause/layout |\n| `actop/tui/widgets.py` | `HardwareDashboard` widget (four titled sections, `grid`/`stack` layout presets with width auto-degrade) plus the custom `BrailleChart` sparkline widget, core rows, and alert computation. TUI CSS is embedded inline as each component's `DEFAULT_CSS` (no separate `.tcss` file). |\n\n```mermaid\ngraph TD\n    subgraph \"macOS Frameworks\"\n        IOR[libIOReport.dylib]\n        CF[CoreFoundation.framework]\n        IOKIT[IOKit Registry\u003cbr/\u003eioreg pmgr device]\n    end\n\n    subgraph \"macOS System Commands\"\n        SYSCTL[sysctl\u003cbr/\u003eCPU brand, core counts]\n        SYSPROF[system_profiler\u003cbr/\u003eGPU core count]\n    end\n\n    subgraph \"Python Libraries\"\n        TEXTUAL[textual\u003cbr/\u003eterminal TUI framework]\n    end\n\n    subgraph \"actop Modules\"\n        IORPY[ioreport.py\u003cbr/\u003ectypes bindings]\n        SAMPLER[sampler.py\u003cbr/\u003eIOReportSampler]\n        SMC[smc.py\u003cbr/\u003eSMC temperature reader]\n        NATIVESYS[native_sys.py\u003cbr/\u003ectypes: RAM, swap,\u003cbr/\u003eprocess enumeration]\n        UTILS[utils.py\u003cbr/\u003esystem context]\n        PROFILES[soc_profiles.py\u003cbr/\u003eM1-M4 profiles]\n        POWER[power_scaling.py\u003cbr/\u003echart scaling]\n        CONFIG[config.py\u003cbr/\u003eDashboardConfig]\n        TUI[tui/app.py + widgets.py\u003cbr/\u003eTextual dashboard]\n        MAIN[actop.py\u003cbr/\u003eCLI entry point]\n    end\n\n    IOR --\u003e|ctypes.cdll| IORPY\n    CF --\u003e|ctypes.cdll| IORPY\n    IOKIT --\u003e|native ctypes| SAMPLER\n\n    IORPY --\u003e|IOReportSubscription\u003cbr/\u003esample/delta| SAMPLER\n    SMC --\u003e|TemperatureReading| SAMPLER\n    SAMPLER --\u003e|SampleResult| TUI\n\n    SYSCTL --\u003e UTILS\n    SYSPROF --\u003e UTILS\n    NATIVESYS --\u003e UTILS\n    UTILS --\u003e|SoC info, RAM, processes| TUI\n\n    PROFILES --\u003e CONFIG\n    CONFIG --\u003e TUI\n    POWER --\u003e TUI\n    TEXTUAL --\u003e TUI\n    TUI --\u003e MAIN\n```\n\n## Troubleshooting\n\n- **Bandwidth shows N/A**: IOReport does not expose memory bandwidth counters on all SoCs.\n- **Thermal shows \"Unknown\"**: ObjC runtime failed to read `NSProcessInfo.thermalState` (unexpected on macOS 12+).\n- **Frequencies show 0 MHz**: DVFS table discovery failed. File an issue with `sysctl -n machdep.cpu.brand_string` output.\n- **Metric differences vs other tools**: expected due to sampling window and source timing differences.\n- **ANE reads `0% (0.0W)`**: expected when idle — the Neural Engine is power-gated unless an app runs CoreML inference. To confirm the gauge works, drive a load with `scripts/ane_load.py` (see Development) or trigger on-device ML (Photos library scan, Live Text, dictation). Note: MLX/Ollama/llama.cpp use the **GPU**, not the ANE.\n\n## Development\n\n```bash\n.venv/bin/python -m pip install -e \".[dev]\"    # install editable + dev deps\n.venv/bin/python -m actop.actop --help         # validate CLI\n.venv/bin/python -m actop.actop                # run with defaults\n.venv/bin/pytest -q                            # run tests\n.venv/bin/python -m ruff check . \u0026\u0026 .venv/bin/python -m ruff format .   # lint + format\n```\n\n### Exercising the ANE gauge\n\nThe Neural Engine idles at `0% (0.0W)` unless something runs CoreML inference.\n`scripts/ane_load.py` generates a deterministic ANE load so you can verify the\ngauge. Its deps (`coremltools`, `numpy`) are macOS-only and heavy, so they live\nin a separate `ane` extra — deliberately kept out of `dev` so Linux CI stays lean.\n\n```bash\n.venv/bin/python -m pip install -e \".[ane]\"     # one-time, macOS only\n.venv/bin/python scripts/ane_load.py --duration 60   # then watch actop's ANE gauge\n```\n\n## Contributing\n\n**PRs and issues welcome.** Bug reports, new SoC profiles, metric fixes, and docs\nimprovements are all appreciated — for anything larger than a small fix, open an\nissue first. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, the branch/PR flow,\nand the functional-tests-only policy (`CLAUDE.md` is the full source of truth).\n\n## Release\n\nSee `GUIDE-release-operations.md` for the full runbook.\n\n```bash\n# 1. Bump version and changelog\nedit pyproject.toml CHANGELOG.md\n\n# 2. Run checks\n.venv/bin/python -m ruff check --fix . \u0026\u0026 .venv/bin/python -m ruff format .\n.venv/bin/python -m actop.actop --help\n.venv/bin/pytest -q\n\n# 3. Commit and tag\ngit add pyproject.toml CHANGELOG.md\ngit commit -m \"Release v$VERSION\"\nscripts/tag_release.sh \"$VERSION\"\n\n# 4. Verify\nbrew update \u0026\u0026 brew upgrade binlecode/actop/actop\n```\n\nCI handles formula sync automatically on tag push.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbinlecode%2Factop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbinlecode%2Factop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbinlecode%2Factop/lists"}