Skip to content

monitor-exporter: native Prometheus /metrics for SMC and GPU (#58) - #59

Merged
evandhoffman merged 8 commits into
mainfrom
worktree-issue-58-monitor-exporter
Sep 14, 2026
Merged

evandhoffman merged 8 commits into
mainfrom
worktree-issue-58-monitor-exporter

Conversation

@evandhoffman

Copy link
Copy Markdown
Contributor

Implements #58: a monitor-exporter binary that serves this Mac's SMC and GPU
sensors as Prometheus metrics on GET /metrics, for a Prometheus server to
scrape.

Why it lives here

The sensor readers already exist in MonitorSources, native and tested. So this
is the native-Swift answer to evanwtf/local-llm#361, which debated wrapping
macmon or reimplementing IOReport in Go — neither is needed. No new package
dependency: the exposition format is rendered in-house (a golden-file test pins
it), and the endpoint is one NWListener route.

What it does

  • Own-port /metrics, read at scrape time — the scrape interval is the
    sampling rate, nothing to configure. A dead exporter fails the scrape
    (up == 0) rather than serving a stale reading.
  • Scope: the macOS gap only. node_exporter reads CPU/memory/disk/network on
    darwin; this exports what it cannot — the SMC (temperature, fans, power) and
    the GPU — so nothing is scraped twice. (--all for the rest is deferred, YAGNI.)
  • Gap, never zero. A sensor this machine lacks produces no series. Validated
    on the dev machine, a fanless MacBook Air: macos_smc_fan_rpm is correctly
    absent, while temperatures, power and GPU are present.
  • --bind-port default 9650 (verified free in the Prometheus
    default-port-allocations registry), --bind-address default loopback.

Metric names: macos_smc_temperature_celsius{sensor}, macos_smc_fan_rpm{fan},
macos_smc_power_watts{rail}, macos_gpu_utilization_ratio,
macos_gpu_vram_used_bytes, plus monitor_exporter_build_info{version,commit}.
Celsius only (no °F twin); no hostname label (instance covers it).

Structure

  • MonitorPrometheus (new library, pure MonitorCore): exposition renderer +
    MetricID→metric mapping.
  • MonitorExporter (new executable monitor-exporter): reads
    SourceRegistry.make(ids: ["sensors", "gpu"]) at scrape time, serves the body.
    Never links MonitorLog/MonitorStore — it writes no files.

Tests

+22 tests (257 → 279), all green: golden-file exposition format, the mapping
against the real SMCSource/GPUSource MetricID constants (a rename breaks
the test, not production), the handler with a fake source (maps known, drops
unmapped, always emits build_info), pure HTTP routing, and one real
bound-port scrape. CI adds a --help/--version smoke and boots the endpoint
to scrape it once. The release zip now ships monitor-exporter beside monitord.

Design captured in #58; plan in docs/superpowers/plans/2026-09-13-monitor-exporter.md.
Docs: docs/exporter.md (launchd plist + scrape config), README and AGENTS updated.

Closes #58.

@evandhoffman
evandhoffman merged commit c8f433b into main Sep 14, 2026
2 checks passed
@evandhoffman
evandhoffman deleted the worktree-issue-58-monitor-exporter branch September 14, 2026 02:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request release:minor Merging this bumps the minor version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

monitor-exporter: native Prometheus /metrics for SMC/power (Swift, reuses MonitorSources)

1 participant