Lightweight monitoring for Ktor services, written in Kotlin with a focus on portability and extremely low overhead.
Unlike traditional monitoring stacks, metrik is one self-contained binary compiled with Kotlin/Native and one SQLite file. No JVM, no time-series database, no scrape configuration. The agent costs the monitored service 106 nanoseconds per request and one UDP packet per minute.
The terminal client. There is a browser dashboard too — see below.
metrik answers four questions — how many requests, how many errors, how slow, is memory running out — and writes to Telegram when the answers get worse.
- Ktor plugin that measures and aggregates in-process, no scraping and no service discovery
- Percentiles from exponential histograms merged across instances, not averaged
- Route templates as series labels (
/users/{id}), so cardinality stays bounded - Deploy markers on every chart: "worse" is only useful with "worse after what"
- Alerts on error rate, latency, memory and silence, with hysteresis and a cooldown
- Zero-runtime-dependency deployment via Kotlin/Native
- SQLite storage using sqlx4k for multiplatform database access
- Authentication via reverse proxy (supports OAuth2-Proxy, Traefik, Nginx)
- Compose Multiplatform dashboard, light/dark theme
- Terminal client — the same numbers without a browser, one native binary
- MCP endpoint, so an agent can read the metrics instead of looking at a page
- Kotlin Multiplatform (JVM, linuxX64, linuxArm64, macosArm64)
- Ktor server plugin API, ktor-network for UDP
- No JMX:
RuntimeandProcessHandleon the JVM,/procon Linux
- Ktor (native CIO engine)
- Kotlin/Native
- SQLite (sqlx4k)
- kotlinx.serialization
- Compose Multiplatform for Wasm — production
- Desktop JVM target — development only, because the wasm build is slow
- MaterialKolor for the Material 3 colour scheme
The bundle is served by the native server itself — staticFiles is unavailable on Kotlin/Native,
so the handful of things it does (MIME, ETag, precompressed twins) are written out by hand.
- Kotlin/Native (linuxX64, linuxArm64, macosArm64), one binary, no runtime
- Mosaic — the Compose runtime, drawing into a terminal
- Charts made of characters, with colour driven by the alerting thresholds
It reads through the MCP endpoint rather than the HTTP API: /api sits behind the browser login,
and a terminal cannot pass that. /mcp is the door already built for machines.
install(Metrik) {
service = "orders-api"
apiKey = System.getenv("METRIK_KEY") // one key per installation, not per service
endpoint = "metrik-ingest:9999"
release = System.getenv("APP_VERSION") // optional; draws deploy markers on charts
}That is the whole setup. Services are not registered anywhere — the first packet creates one.
repositories {
mavenCentral()
maven {
name = "WipSnapshots"
url = uri("https://reposilite.kotlin.website/snapshots")
}
}
dependencies {
implementation("ru.workinprogress.metrik:agent:$metrik_version")
}The agent never blocks a request and never throws into the host pipeline. If the server is unreachable, the DNS name does not resolve or the queue overflows, it counts the loss and keeps serving traffic.
docker run -p 8080:8080 -p 9999:9999/udp \
-v ./data:/data \
-e METRIK_INGEST_KEY=<secret> \
ghcr.io/youndie/metrik:latestThe dashboard ships inside the same image and is served by the same binary: one container, one
port, one release artifact. It can be switched off (web.enabled: false in the chart) for
installations that live in the terminal — the API, the alerting and MCP carry on without it.
Two doors, one token. The MCP endpoint appears only when METRIK_MCP_TOKEN is set: no token means
no route, no secret and no ingress bypass — an absent setting must mean closed, not open.
An agent connects to https://<host>/mcp and gets seven read-only tools: which services are
reporting, an overview, the slow routes, the 5xx, the time series, the deploys and the firing
alerts. See docs/api/mcp-tools.md.
The terminal client speaks the same protocol:
export METRIK_URL=https://metrik.example.com
export METRIK_TOKEN=<the same token>
metrik # services, with firing alerts marked
metrik orders-api # one service: latency chart, slow routes, errors, deploysA minute nobody reported is drawn as a gap rather than a zero, colour follows the alerting
thresholds and is doubled by a dashed threshold guide, and NO_COLOR is honoured — the chart has
to survive a pipe and a monochrome terminal. Details in
docs/services/metrik-cli.md.
metrik does not implement its own user login. Instead it trusts upstream authentication headers provided by middleware such as:
- oauth2-proxy
- Traefik ForwardAuth
- NGINX auth_request
metrik reads the following headers:
X-Auth-Request-User— unique user identifierX-Auth-Request-Email— user email
If these headers are missing, metrik returns 401 Unauthorized. Do not run it without such a proxy — the dashboard would be open to anyone who reaches the port.
The MCP endpoint is the exception, and deliberately so: a machine cannot fill in a login form, so
/mcp bypasses the proxy and is guarded by its own bearer token instead. The proxy's headers are
not accepted there — anyone reaching that route could claim any identity in them.
METRIK_ADMINS narrows the admin routes to a list of emails. Left empty, every authenticated user
is an admin: an installation belongs to one team.
authResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-EmailThe ingest port speaks UDP and is not authenticated in any meaningful sense: the key in the packet stops accidents, not attackers. Keep it inside the cluster and never expose it through an ingress.
metrik is designed to run on Kubernetes. We provide an official Helm chart.
👉 Read the Deployment Guide to learn how to install metrik with Helm, configure Traefik IngressRoute, and set up SSO integration.
- Not a tracing system. No spans, no correlation ids. metrik answers "is this service in trouble", not "why was this one request slow".
- Not an SLA reporting tool. Percentiles come from histogram buckets, so they carry up to 20% relative error. Enough for trends and alerts, not for a contract.
- Not multi-tenant. One installation belongs to one team. Need isolation? Run a second one — it is one binary and one file.
docs/ — architecture research with the reasoning behind every decision, features with BDD scenarios, the wire protocol, and per-service documentation. Written in Russian.
The research document is worth reading before changing anything: several decisions here are counter-intuitive and were made against evidence, not taste.
MIT.
