package server import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" ) // cacheStats is the read side of the NetBox cache the collector publishes. type cacheStats interface { Hits() int64 Misses() int64 } // gitStats is the read side of the template git-syncer the collector publishes. type gitStats interface { Syncs() int64 Failures() int64 Generation() int64 } // metrics holds bootapi's Prometheus instruments, registered on a private // registry so tests can construct isolated servers. type metrics struct { reg *prometheus.Registry httpRequests *prometheus.CounterVec // by endpoint,status renders *prometheus.CounterVec // by kind,result netboxLookups *prometheus.CounterVec // by field,result netboxDuration *prometheus.HistogramVec provisioned *prometheus.CounterVec // by result ipxeGated prometheus.Counter } func newMetrics(cache cacheStats, git gitStats) *metrics { reg := prometheus.NewRegistry() m := &metrics{ reg: reg, httpRequests: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "bootapi_http_requests_total", Help: "HTTP requests handled, by endpoint and status class.", }, []string{"endpoint", "status"}), renders: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "bootapi_render_total", Help: "Template renders, by kind (kickstart|ipxe) and result (ok|error).", }, []string{"kind", "result"}), netboxLookups: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "bootapi_netbox_lookups_total", Help: "NetBox host resolutions, by field (mac|name) and result (ok|notfound|error).", }, []string{"field", "result"}), netboxDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "bootapi_netbox_lookup_duration_seconds", Help: "Latency of NetBox host resolutions.", Buckets: prometheus.DefBuckets, }, []string{"field"}), provisioned: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "bootapi_provisioned_total", Help: "Provisioned callbacks, by result (ok|unauthorized|notfound|error|disabled).", }, []string{"result"}), ipxeGated: prometheus.NewCounter(prometheus.CounterOpts{ Name: "bootapi_ipxe_gated_total", Help: "Known hosts served the local-boot fallback because pxe_enabled=false.", }), } reg.MustRegister(m.httpRequests, m.renders, m.netboxLookups, m.netboxDuration, m.provisioned, m.ipxeGated) if cache != nil { reg.MustRegister(newCacheCollector(cache)) } if git != nil { reg.MustRegister(newGitCollector(git)) } reg.MustRegister( collectors.NewGoCollector(), collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), ) return m } // gitCollector publishes the template git-syncer counters. type gitCollector struct { stats gitStats syncs *prometheus.Desc failures *prometheus.Desc generation *prometheus.Desc } func newGitCollector(s gitStats) *gitCollector { return &gitCollector{ stats: s, syncs: prometheus.NewDesc("bootapi_template_sync_total", "Successful template reloads from git.", nil, nil), failures: prometheus.NewDesc("bootapi_template_sync_failures_total", "Template git pull/parse failures (last-good kept).", nil, nil), generation: prometheus.NewDesc("bootapi_template_generation", "Monotonic counter of the active template generation.", nil, nil), } } func (c *gitCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.syncs ch <- c.failures ch <- c.generation } func (c *gitCollector) Collect(ch chan<- prometheus.Metric) { ch <- prometheus.MustNewConstMetric(c.syncs, prometheus.CounterValue, float64(c.stats.Syncs())) ch <- prometheus.MustNewConstMetric(c.failures, prometheus.CounterValue, float64(c.stats.Failures())) ch <- prometheus.MustNewConstMetric(c.generation, prometheus.GaugeValue, float64(c.stats.Generation())) } // cacheCollector publishes the NetBox cache hit/miss counters, which live on // the Cache itself (atomic ints) rather than in a CounterVec. type cacheCollector struct { stats cacheStats hits *prometheus.Desc miss *prometheus.Desc } func newCacheCollector(s cacheStats) *cacheCollector { return &cacheCollector{ stats: s, hits: prometheus.NewDesc("bootapi_netbox_cache_hits_total", "NetBox cache hits.", nil, nil), miss: prometheus.NewDesc("bootapi_netbox_cache_misses_total", "NetBox cache misses.", nil, nil), } } func (c *cacheCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.hits ch <- c.miss } func (c *cacheCollector) Collect(ch chan<- prometheus.Metric) { ch <- prometheus.MustNewConstMetric(c.hits, prometheus.CounterValue, float64(c.stats.Hits())) ch <- prometheus.MustNewConstMetric(c.miss, prometheus.CounterValue, float64(c.stats.Misses())) }