package metrics import ( "testing" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil/promlint" ) // Verifies that all exported PVE metrics follow the Prometheus naming conventions // (base units, '_total' suffix on counters, '_info' suffix on info metrics, ...) // so that tools such as promtool or Grafana do not report naming problems. func TestPveMetricNamesFollowPrometheusConventions(t *testing.T) { registry := prometheus.NewRegistry() // Collectors register their metrics on the default registerer, so it is // temporarily replaced by a dedicated registry. defaultRegisterer := prometheus.DefaultRegisterer prometheus.DefaultRegisterer = registry defer func() { prometheus.DefaultRegisterer = defaultRegisterer }() // Collector constructors only create metrics, the API client is used during // collection only, so a nil client is enough here. ttlRegistry := NewTTLRegistry() NewPveClusterStateCollector(nil, ttlRegistry) NewPveNodeStatusCollector(nil, ttlRegistry) NewPveSubscriptionCollector(nil, ttlRegistry) NewPveNodeDiskCollector(nil, ttlRegistry) NewPveNodeZfsCollector(nil, ttlRegistry) NewPveSdnCollector(nil, ttlRegistry) NewPveStorageCollector(nil, ttlRegistry) NewPveContainerCollector(nil, ttlRegistry) NewPveVirtualMachineCollector(nil, ttlRegistry) // Metric vectors are exported only once they hold a label set. for _, metric := range ttlRegistry.metrics { switch m := metric.(type) { case *TTLGaugeVec: m.With(emptyLabels(m.labelNames)).Set(0) case *TTLCounterVec: m.With(emptyLabels(m.labelNames)).Set(0) default: t.Fatalf("Unknown TTL metric type %T.", metric) } } families, err := registry.Gather() if err != nil { t.Fatalf("Unable to gather metrics. Error: %s.", err) } if len(families) != len(ttlRegistry.metrics) { t.Fatalf("Gathered %d metric families but %d metrics are registered.", len(families), len(ttlRegistry.metrics)) } problems, err := promlint.NewWithMetricFamilies(families).Lint() if err != nil { t.Fatalf("Unable to lint metrics. Error: %s.", err) } for _, problem := range problems { t.Errorf("Metric '%s' violates the Prometheus naming conventions: %s.", problem.Metric, problem.Text) } } // emptyLabels builds a label set with all given label names set to an empty value. func emptyLabels(labelNames []string) prometheus.Labels { labels := prometheus.Labels{} for _, name := range labelNames { labels[name] = "" } return labels }