Documentation

Everything needed to run TinyObs, get metrics into it, and query them back out.

01Quick start

TinyObs is one Go binary with an embedded store. There is nothing else to run. The dashboard and API both listen on port 8080.

docker
git clone https://github.com/nicktill/tinyobs && cd tinyobs
docker compose up -d
go 1.23+
go run ./cmd/server

To generate demo traffic, run the example app with go run ./cmd/example or make docker-demo, then open http://localhost:8080.

02Instrument an app

The Go SDK batches samples and pushes them every few seconds. The HTTP middleware records http_requests_total and http_request_duration_seconds, labelled by the route pattern your ServeMux matched, so IDs in URLs never become new series.

main.go
client, _ := sdk.New(sdk.ClientConfig{
    Service:  "my-app",
    Endpoint: "http://localhost:8080/v1/ingest",
})
client.Start(ctx)
defer client.Stop()

mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser)
handler := httpx.Middleware(client)(mux)  // path="/users/{id}"

errors := client.Counter("errors_total")
errors.Inc("type", "api_error")

03Remote write

Anything that speaks Prometheus remote write 1.0 can push to TinyObs: Prometheus, Grafana Alloy, the OpenTelemetry Collector, vmagent.

prometheus.yml
remote_write:
  - url: http://localhost:8080/api/v1/write

If TINYOBS_AUTH_TOKEN is set, add authorization: { credentials: <token> } to the entry. The Go SDK sends it from ClientConfig.APIKey.

Samples that can never be stored (out of order, over a series limit, invalid labels) get a 400, so senders drop them instead of retrying forever. Storage errors get a 5xx and are retried.

04PromQL

The engine runs Prometheus's own promqltest suite (make promql-compat). A supported feature returns what Prometheus returns. Anything else is an explicit “not supported” error, never a guess.

supported

  • Selectors with = != =~ !~, range vectors, offset, subqueries
  • Arithmetic, comparison (with bool) and set operators with on, ignoring, group_left, group_right
  • sum avg min max count group stddev stdvar topk bottomk quantile
  • rate irate increase delta idelta deriv predict_linear resets changes
  • *_over_time, histogram_quantile, math, clamping, date and label functions

not supported

  • Native histograms
  • The @ modifier and duration expressions
  • count_values, limitk
  • holt_winters, sort_by_label
shell
curl -s localhost:8080/api/v1/query \
  --data-urlencode 'query=histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))'

05HTTP API

Request parameters, response shapes and error types follow the Prometheus HTTP API.

endpointpurpose
/api/v1/queryInstant query
/api/v1/query_rangeRange query
/api/v1/seriesSeries matching selectors
/api/v1/labelsLabel names
/api/v1/label/<name>/valuesValues of one label
/api/v1/metadataMetric type and help text
/api/v1/status/tsdbSeries count and the heaviest metrics and labels
POST /api/v1/writePrometheus remote write 1.0
POST /v1/ingestGo SDK ingest
/metricsTinyObs's own metrics
/-/healthy, /-/readyHealth checks

06Configuration

Configured with environment variables. Invalid values stop the server at startup rather than falling back to a default.

variabledefaultdescription
TINYOBS_LISTEN127.0.0.1:8080Listen address. Loopback by default; the Docker image uses 0.0.0.0:8080
PORT8080Port, when TINYOBS_LISTEN is unset
TINYOBS_AUTH_TOKENunsetBearer token required on the write endpoints
TINYOBS_DATA_DIR./data/tinyobs-v2Data directory
TINYOBS_RETENTION72hHow long samples are kept
TINYOBS_MAX_SERIES50000Total series limit
TINYOBS_MAX_SERIES_PER_METRIC10000Series limit per metric name
TINYOBS_MAX_MEMORY_MB64Storage memory budget

07Cardinality & retention

Every unique label set is a series, and series are what cost memory and disk. TinyObs caps them twice. The global limit bounds the whole server. The per-metric limit stops one runaway label, such as a user ID, from using up the global budget for every other metric.

Pushed series have no scrape to fail. When a service stops, its series keep their last value for the 5-minute lookback window, then drop out of instant queries.

Retention is time-based and runs hourly, without pausing ingestion. Disk use is predictable:

sizing
disk ≈ series × samples per series in the window × ~14 bytes

# 5,000 series, 15s interval, 72h retention
5,000 × 17,280 × 14 B ≈ 1.2 GB

Rejected samples are never dropped silently. Alert on the counter, and use the status endpoint to find the metric responsible:

shell
curl -s localhost:8080/metrics | grep -E 'tinyobs_(series|samples_rejected)'
curl -s localhost:8080/api/v1/status/tsdb | jq '.data.seriesCountByMetricName'

08Grafana

Add a Prometheus data source with the URL http://localhost:8080 (from a container, http://host.docker.internal:8080). Existing dashboards work, as long as their queries stay within the supported PromQL above.