Prometheus metrics at /metrics, configured in jarl.yaml

New metrics: section (enabled, listen, allow, token). /metrics is served on
the main listeners, the local socket (root and jarld's user) and optional
metrics-only addresses; a scrape needs an allowed address or the bearer
token. Exports health (named up, problems, drift, backups, blocklists,
DNSSEC phases, token expiry) and, with stats on, named's and Jarl's query
counters since jarld started plus named's process and cache gauges.
docs/metrics.md has example queries and alerts checked with promtool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-11 22:33:41 +01:00
1 parent 7e6e274a89
commit c37de31f4c
17 files changed
+1126 -4

No files matched your search

+21 -1
View File
@@ -114,6 +114,9 @@ type Collector struct {
// statistics (server.go).
proc *namedProc
server *Server
// totals are the counters added up since jarld started (gauges left
// out), for Prometheus (Totals).
totals map[string]int64
}
// maxKeys bounds the per-hour maps; rarely seen keys are dropped first.
@@ -125,7 +128,7 @@ func NewCollector(store *Store, opts Options, zones ZoneIndex, log *slog.Logger)
opts.Location = time.Local
}
c := &Collector{opts: opts, store: store, zones: zones, log: log, poller: NewPoller(opts.StatisticsChannel), now: time.Now,
minutes: map[int64]map[string]int64{}, hours: map[int64]map[string]map[string]int64{}, late: map[int64]map[string]map[string]int64{}}
minutes: map[int64]map[string]int64{}, totals: map[string]int64{}, hours: map[int64]map[string]map[string]int64{}, late: map[int64]map[string]map[string]int64{}}
if opts.QueryLog {
c.tail = &Tailer{Path: opts.LogFile}
}
@@ -286,6 +289,23 @@ func (c *Collector) count(minute int64, key string, n int64) {
c.minutes[minute] = m
}
m[key] += n
if !strings.HasPrefix(key, "g:") && !strings.HasPrefix(key, "gn:") {
c.totals[key] += n
}
}
// Totals returns every counter added up since the collector started (the
// keys of docs/stats.md, without gauges). They only grow, so they suit
// Prometheus counters; named's counters move once a minute, when they are
// polled.
func (c *Collector) Totals() map[string]int64 {
c.mu.Lock()
defer c.mu.Unlock()
out := make(map[string]int64, len(c.totals))
for k, v := range c.totals {
out[k] = v
}
return out
}
// readLog reads new response log lines.
+3 -1
View File
@@ -14,7 +14,8 @@ type Server struct {
PID int `json:"pid,omitempty"`
RSS int64 `json:"rss,omitempty"` // resident memory, bytes
Threads int64 `json:"threads,omitempty"`
CPUPercent float64 `json:"cpu_percent"` // of one CPU, over the last minute
CPUPercent float64 `json:"cpu_percent"` // of one CPU, over the last minute
CPUSeconds float64 `json:"cpu_seconds,omitempty"` // user + system time since named started
// From the statistics channel.
MemInUse int64 `json:"mem_in_use"` // named's own count of the memory it uses
CacheRRsets int64 `json:"cache_rrsets"`
@@ -63,6 +64,7 @@ func (c *Collector) sampleServer(minute int64, now time.Time, snap *Snapshot) {
c.proc = nil
} else {
srv.PID, srv.RSS, srv.Threads = p.PID, p.RSS, p.Threads
srv.CPUSeconds = float64(p.CPU) / clockTicks
if prev := c.proc; prev.At.Before(now) && !prev.At.IsZero() && p.CPU >= prev.CPU {
// Tenths of a percent of one CPU: ticks / clockTicks is CPU seconds.
permille := (p.CPU - prev.CPU) * 1000 / clockTicks / max(int64(now.Sub(prev.At)/time.Second), 1)