Quickstart: publish metrics¶
In five minutes you'll publish a gauge and a labelled counter from a Go
program, read them with systemd's own systemd-report, and see them through
sdmetrics-exporter. You need Go 1.26+ and a Linux host. systemd v260+ is
needed only for the systemd-report step.
1. Create a module¶
2. Write the program¶
The provider part is ten lines: create it, register two metrics, update them, serve.
package main
import (
"context"
"flag"
"log"
"os"
"os/signal"
"time"
"github.com/rossedman/sdmetrics/provider"
)
func main() {
dir := flag.String("dir", provider.DefaultDir, "report directory") // (1)!
flag.Parse()
p, err := provider.New("io.example.Queue", provider.WithDir(*dir)) // (2)!
if err != nil {
log.Fatal(err)
}
depth := p.Gauge("Depth", provider.Help("Jobs waiting")) // (3)!
jobs := p.Counter("JobsProcessed", provider.Help("Jobs finished"), provider.Labels("result"))
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
go func() { // a stand-in for real work
for ctx.Err() == nil {
depth.Set(float64(time.Now().Second()))
jobs.With("ok").Inc() // (4)!
time.Sleep(time.Second)
}
}()
log.Printf("publishing on %s", p.Path())
if err := p.Serve(ctx); err != nil { // (5)!
log.Fatal(err)
}
}
/run/systemd/reportis where consumers look, and writing there needs root.-dir /tmp/reportlets you try the program as any user.- The provider name is a Varlink interface name in reverse-domain style. It becomes the socket's file name, and the prefix of every family.
- Registration validates names and panics on any name
systemd-reportwould drop, so mistakes show up at startup. Withtakes one value per declared label. Keep the handle it returns on hot paths:ok := jobs.With("ok").Servebinds the socket, answersio.systemd.Metrics.ListandDescribe, and removes the socket when the context ends.
3. Run it¶
In a second terminal, read it back with the exporter's -once mode:
SOURCE UNIT FAMILY TYPE OBJECT FIELDS VALUE
io.example.Queue session-2.scope io.example.Queue.Depth gauge - - 17
io.example.Queue session-2.scope io.example.Queue.JobsProcessed counter - result=ok 17
1 sources, 2 values in 1.2ms.
The UNIT column is the unit your shell runs in, here a login session's
scope: the scraper resolved it from the socket's peer credentials. Run the program as a service and it shows that
service instead.
4. Read it with systemd¶
On a systemd v260+ host, run the program as root against the real directory, and systemd's own tool can read it:
systemd-report metrics io.example.Queue
systemd-report describe io.example.Queue # describe-metrics on v260 and v261
Or speak Varlink directly:
{"name":"io.example.Queue.Depth","value":17}
{"name":"io.example.Queue.JobsProcessed","fields":{"result":"ok"},"value":17}
What just happened¶
sequenceDiagram
autonumber
participant P as your program
participant S as /run/systemd/report/io.example.Queue
participant C as systemd-report / sdmetrics
P->>S: Serve binds the socket (mode 0660)
C->>S: connect, read SO_PEERCRED
C->>S: io.systemd.Metrics.Describe (more)
S-->>C: name, description, type per family
C->>S: io.systemd.Metrics.List (more)
S-->>C: name, fields, value per series
provider.Newchecked the name and prepared the socket path; nothing was bound yet.Servebound the socket and servedio.systemd.Metricswith sdvarlink'svarlink.Server.- Consumers called
Describefor types andListfor values. Each value read the current atomic, so no lock was taken against your updates.
Stuck?
permission deniedbinding the socket:/run/systemd/reportneeds root. Use-dir /tmp/report, or let systemd bind it with socket activation.systemd-reportdoesn't list your metrics: check the family names start with the socket's name. The provider guarantees this; a hand-written source might not. See Naming rules.- The exporter can't read the socket: the default mode is
0660. See Socket activation & permissions.
Where next¶
-
The provider in depth
Every metric type and option, and how updates stay allocation-free.
-
Run it as a service
A
.socketunit,FromActivation, and who may read the socket. -
Already instrumented?
Publish a Prometheus registry or OTel reader without changes.
-
A fuller example
examples/queued: strings, objects, callbacks and unit files.