TheWatcher

A self-hosted, single-binary system metrics viewer that collects CPU, memory, disk, and network metrics, persists them locally with RRD-style rollups, and serves a browser dashboard — no cloud, no database, no telemetry.

# What is TheWatcher?

You want to see what your server is doing right now and over time without shipping metrics to a SaaS platform or standing up a full monitoring stack. TheWatcher gives you a single binary to copy and run, a browser dashboard with gauges and line charts, and durable local history with predictable retention — all from one Rust binary.

~4.5 MB
Binary size (release)
64
Tests across all modules
6
Metric collectors
5
Retention resolutions

# Features

One binary to copy and run. No cloud, no telemetry, no accounts.

📊 Browser dashboard

Embedded web UI with real-time gauges and historical line charts. No CDN, no external assets — everything is baked into the binary.

Rust + Axum

💾 RRD-style rollups

Granular 30-second samples rolled up into hourly, daily, monthly, and yearly summaries. Configurable retention at every level.

🔍 Read-only JSON API

All API endpoints are read-only — no configuration changes, no command execution, no file browsing. Safe to expose on your network.

🧠 MooFile storage

Metrics stored in append-only BSON files via MooFile. Deterministic IDs make rollups idempotent and restart-safe.

Embedded

🛡️ Safe by default

Binds to 127.0.0.1 (loopback only) unless overridden. Runs as unprivileged user. Graceful degradation when metrics are unavailable.

📡 Multi-platform

Linux, macOS, Windows. systemd, OpenRC, runit, launchd — install as a service on any OS. Cross-compile for ARM64 too.

TheWatcher dashboard gauges
Dashboard gauges — real-time system metrics
TheWatcher dashboard charts
Historical charts — CPU, memory, disk, network

# Quick Start

Get TheWatcher running in under a minute.

Build & run

# Build from source
git clone https://github.com/patw/thewatcher.git
cd thewatcher
cargo build --release

# Run with defaults — listens on 127.0.0.1:8080
./target/release/thewatcher

# Open the dashboard
open http://127.0.0.1:8080

Command-line options

OptionDefaultDescription
--listen127.0.0.1Bind address
--port8080HTTP port
--interval30sCollection interval
--data-dir~/.local/share/thewatcherData directory
--granular-retention30dRetain granular samples
--hourly-retention365dRetain hourly summaries
--daily-retention5yRetain daily summaries
--monthly-retention10yRetain monthly summaries
--yearly-retention0 (indefinite)Retain yearly summaries

Remote access

# Safe: SSH tunnel (recommended)
ssh -L 8080:127.0.0.1:8080 admin@server
# Then open http://127.0.0.1:8080

# Direct management network
thewatcher --listen 192.168.10.25 --port 8080

# HTTP API

All endpoints are read-only. Responses are JSON. Perfect for scripting and automation.

GET /api/health
Basic application health — returns 200 when healthy, 503 when storage is unavailable.
GET /api/info
Host and application information — hostname, OS, arch, uptime, version.
GET /api/current
In-memory snapshot of all current metrics — CPU, memory, disk, network with per-component health status.
GET /api/history
Historical data with auto-selected resolution. Supports metric, range, from/until, resolution, interface, mount params.

History query parameters

ParameterDescription
metriccpu, memory, disk, network, sockets
range1h, 24h, 7d, 30d, 1y
from/untilCustom epoch milliseconds (mutually exclusive with range)
resolutiongranular, hourly, daily, monthly, yearly, or auto
interfaceNetwork interface filter (for network metric)
mountDisk mount filter (for disk metric)
# Last hour of CPU at granular resolution
curl -s "http://127.0.0.1:8080/api/history?metric=cpu&range=1h" | jq

# Last 30 days of memory
curl -s "http://127.0.0.1:8080/api/history?metric=memory&range=30d" | jq

# Network for eth0, last 7 days
curl -s "http://127.0.0.1:8080/api/history?metric=network&interface=eth0&range=7d" | jq

# Architecture

Platform collectors → in-memory snapshot → MooFile storage → background rollups → HTTP API + embedded web UI.

Platform collectors In-memory snapshot Granular MooFile writer Background rollups HTTP API + Web UI

📁 Storage layout

Five separate MooFile collections under the data directory:

data-dir/
├── granular.bson   ← raw 30s samples
├── hourly.bson     ← 1-hour rollups
├── daily.bson      ← 1-day rollups
├── monthly.bson    ← 30-day rollups
└── yearly.bson     ← 365-day rollups

Every document has a deterministic _id, making rollup writes idempotent and restart-safe.

⏱ Resolution auto-selection

RangeResolutionBucket
≤ 1 hourgranularraw samples
≤ 1 dayhourly1 hour
≤ 30 daysdaily1 day
≤ 365 daysmonthly30 days
> 365 daysyearly365 days

Project structure

src/
├── main.rs              # Entry point, collection/maintenance loops
├── lib.rs               # Module re-exports
├── cli.rs               # CLI argument parsing (clap)
├── config.rs            # Configuration struct + defaults
├── model.rs             # All data types, resolution logic
├── server.rs            # Axum HTTP server + embedded asset serving
├── api.rs               # Route handlers
├── collectors/          # CPU, memory, disk, network, sockets, uptime
├── storage/             # MooFile manager, rollup worker, retention
└── web/                 # Dashboard HTML, JS, CSS (embedded in binary)