Skip to content

Repository files navigation

metrik

ktlint kotlin native jvm wasm metrik agent Docker Image Version license

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.

metrik-cli: a service, its latency chart and its slowest routes in a terminal

The terminal client. There is a browser dashboard too — see below.

Overview

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

Tech Stack

Agent

  • Kotlin Multiplatform (JVM, linuxX64, linuxArm64, macosArm64)
  • Ktor server plugin API, ktor-network for UDP
  • No JMX: Runtime and ProcessHandle on the JVM, /proc on Linux

Server

  • Ktor (native CIO engine)
  • Kotlin/Native
  • SQLite (sqlx4k)
  • kotlinx.serialization

Dashboard

  • 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.

Terminal client

  • 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.

Instrumenting a service

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.

Add dependencies

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.

Running the server

docker run -p 8080:8080 -p 9999:9999/udp \
  -v ./data:/data \
  -e METRIK_INGEST_KEY=<secret> \
  ghcr.io/youndie/metrik:latest

The 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.

Reading it without a browser

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, deploys

A 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.

Authentication

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 identifier
  • X-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.

For Traefik:

authResponseHeaders:
  - X-Auth-Request-User
  - X-Auth-Request-Email

Ingest port

The 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.

🚀 Deployment

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.

What metrik is not

  • 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.

Documentation

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.

License

MIT.