A scrape-time collector reports scimesh_tasks/jobs/workers gauges keyed by status, sourced from cheap GROUP BY queries (StatsRepo), zero-filled across all known statuses so the dashboard shows flat zeros instead of gaps. A failed query yields no samples for that scrape rather than crashing it. Metrics is now built in main so the DB-backed collector can be registered (NewServer takes *metrics.Metrics; nil self-provisions for tests). Grafana dashboard gains a Domain state row: tasks/jobs/workers by status and a queue- depth stat.
67 lines
2.2 KiB
Go
67 lines
2.2 KiB
Go
package metrics
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
// Stats is a point-in-time snapshot of the coordinator's domain state: counts of
|
|
// tasks, jobs, and workers keyed by their status. Maps are expected to be
|
|
// zero-filled by the provider so every known status is always present, giving
|
|
// the dashboard flat zero lines instead of gaps.
|
|
type Stats struct {
|
|
Tasks map[string]int
|
|
Jobs map[string]int
|
|
Workers map[string]int
|
|
}
|
|
|
|
// StatsFunc returns the current snapshot. It is called on every scrape, so it
|
|
// must be a cheap aggregate query.
|
|
type StatsFunc func(context.Context) (Stats, error)
|
|
|
|
// RegisterBusiness registers a collector that reports domain-state gauges
|
|
// (scimesh_tasks/jobs/workers by status) sourced from collect on each scrape.
|
|
// Deriving the gauges at scrape time keeps them fresh without a background
|
|
// goroutine, and a failed query simply yields no samples for that scrape.
|
|
func (m *Metrics) RegisterBusiness(collect StatsFunc) {
|
|
m.reg.MustRegister(&businessCollector{
|
|
collect: collect,
|
|
tasks: prometheus.NewDesc("scimesh_tasks", "Tasks by status.", []string{"status"}, nil),
|
|
jobs: prometheus.NewDesc("scimesh_jobs", "Jobs by status.", []string{"status"}, nil),
|
|
workers: prometheus.NewDesc("scimesh_workers", "Workers by status.", []string{"status"}, nil),
|
|
})
|
|
}
|
|
|
|
type businessCollector struct {
|
|
collect StatsFunc
|
|
tasks, jobs, workers *prometheus.Desc
|
|
}
|
|
|
|
func (c *businessCollector) Describe(ch chan<- *prometheus.Desc) {
|
|
ch <- c.tasks
|
|
ch <- c.jobs
|
|
ch <- c.workers
|
|
}
|
|
|
|
func (c *businessCollector) Collect(ch chan<- prometheus.Metric) {
|
|
// A bounded query so one slow scrape cannot stall Prometheus.
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
|
|
s, err := c.collect(ctx)
|
|
if err != nil {
|
|
return // no samples this scrape; Prometheus keeps the last value
|
|
}
|
|
emit(ch, c.tasks, s.Tasks)
|
|
emit(ch, c.jobs, s.Jobs)
|
|
emit(ch, c.workers, s.Workers)
|
|
}
|
|
|
|
func emit(ch chan<- prometheus.Metric, desc *prometheus.Desc, counts map[string]int) {
|
|
for status, n := range counts {
|
|
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(n), status)
|
|
}
|
|
}
|