Thirty seconds of sdmetrics¶
package main
import (
"context"
"log"
"os"
"os/signal"
"github.com/rossedman/sdmetrics/provider"
)
func main() {
p, err := provider.New("io.example.Queue") // (1)!
if err != nil {
log.Fatal(err)
}
depth := p.Gauge("Depth", provider.Help("Jobs waiting"))
jobs := p.Counter("JobsProcessed", provider.Labels("result")) // (2)!
depth.Set(42)
jobs.With("ok").Inc()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
if err := p.Serve(ctx); err != nil { // (3)!
log.Fatal(err)
}
}
- The provider name is the socket's file name:
/run/systemd/report/io.example.Queue. Families are published asio.example.Queue.Depthandio.example.Queue.JobsProcessed. - Labels become the
fieldsof each value. Updates are atomic and do not allocate. Servebinds the socket (mode0660), answersio.systemd.Metricsand removes the socket when the context ends.
$ systemd-report metrics io.example.Queue
$ varlinkctl call --more /run/systemd/report/io.example.Queue io.systemd.Metrics.List '{}'
Your daemon is now a report source like io.systemd.Manager. Anything
that reads systemd's report framework reads it too.
What you get¶
-
A provider SDK
Gauges, counters and string metrics with labels and objects, served as
io.systemd.Metricson a report socket. Lock-free, allocation-free updates. -
Bridges for existing code
Already instrumented? Publish a Prometheus registry or an OpenTelemetry SDK reader through a provider in two lines.
-
A host exporter
sdmetrics-exporterscrapes every source on an interval and pushes OTLP, serves/metrics, or both. Shipped as deb and rpm with a hardened unit. -
A Collector receiver
systemdreportbrings the same scrape into the OpenTelemetry Collector, built withmdatagenandscraperhelper. -
Unit identity
Every source is attributed to its systemd unit, from peer credentials and cgroups, or from the manager for socket-activated sources.
-
A documented OTel mapping
One resource per source,
service.namefrom the unit, counters with proper start times, and an optional OTel naming mode.
Why systemd's report framework?¶
Before v260, every daemon that wanted metrics opened its own HTTP port, picked
its own names and left you to work out which service a port belonged to.
systemd v260 adds a report framework: a service serves the
io.systemd.Metrics Varlink interface on a socket under /run/systemd/report/,
and any consumer lists the directory to find every source on the host.
Before: a port per daemon¶
flowchart TB
P["Prometheus"] -- ":9100" --> A["node_exporter"]
P -- ":9558" --> B["your daemon"]
P -- "?" --> C["systemd itself"]
After: one directory per host¶
flowchart TB
E["sdmetrics-exporter<br/>or systemdreport receiver"] -- "list" --> D[("/run/systemd/report/")]
D --> M["io.systemd.Manager"]
D --> N["io.systemd.Network"]
D --> Q["io.example.Queue<br/>(your daemon)"]
No ports, no service discovery, and identity for free: the socket's peer credentials tell the scraper which unit answers. Learn the model in Core concepts.
-
Publishing from Go?
A working provider in ten lines, read back with
systemd-report. -
Operating hosts?
Install the exporter and see every source on the host in a minute.
-
Looking something up?
Every config key, metric name and attribute, with its default.