a prometheus-compatible metrics server in ~6,500 lines of go
Metrics you can read end to end.
PromQL verified against Prometheus's own test suite, a Grafana-ready API, remote write, and a storage engine with real cardinality and retention limits. One binary. No cluster.
$ go run ./cmd/server level=INFO msg="TinyObs started" retention=72h max_series=50000 $ curl -s localhost:8080/api/v1/query \ --data-urlencode 'query=topk(3, sum by (path) ( rate(http_requests_total[5m])))' \ | jq -r '.data.result[] | "\(.metric.path)\t\(.value[1])"' /api/users/{id} 41.2 /api/orders 17.9 /healthz 4.0 $ curl -s localhost:8080/metrics | grep rejected tinyobs_samples_rejected_total{reason="metric_series_limit"} 1204 $
What's inside
The whole system, by package. Lines of Go, not counting tests, comments or blank lines. Every piece is small enough to read in one sitting.
| promql | 2,820 | Lexer, parser and evaluator, checked against upstream promqltest | |
| sdk | 1,266 | Go client: counters, gauges, histograms, HTTP middleware | |
| tsdb | 920 | BadgerDB storage, label index, retention, cardinality limits | |
| api | 725 | Prometheus HTTP API and remote write | |
| server | 484 | Wiring, background retention, /metrics self-monitoring | |
| labels | 345 | Label sets, matchers, validation | |
| total | 6,560 | Prometheus is roughly 50× larger. |
Guardrails, not surprises
The failure modes that take down real metrics systems are handled up front, and every one of them is observable.
Cardinality
A global series cap plus a per-metric cap, so one label explosion is rejected on its own instead of starving every other metric. The SDK labels requests by route pattern and collapses 404s.
TINYOBS_MAX_SERIES_PER_METRIC=10000
Retention
Time-based, applied hourly, with disk that stays predictable. Samples compress to about 14 bytes each on BadgerDB with ZSTD.
disk ≈ series × samples × 14 B
Nothing dropped silently
Every rejected sample is counted by reason and exposed on /metrics. The heaviest metrics and labels are one request away.
tinyobs_samples_rejected_total{reason}
Where it fits
TinyObs trades horizontal scale for something you can hold in your head.
use it for
- +Local development and staging
- +Side projects and single-host services
- +Learning how a TSDB and PromQL actually work
- +Performance checks in CI
not for
- −Highly available production monitoring
- −Multi-tenant workloads
- −Long-term storage
→ use Prometheus, Thanos or Mimir