diff --git a/controller/controller.go b/controller/controller.go index 06562da58..220da28ce 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -37,7 +37,6 @@ import ( var ( registryErrorsTotal = metrics.NewCounterWithOpts( prometheus.CounterOpts{ - Namespace: "external_dns", Subsystem: "registry", Name: "errors_total", Help: "Number of Registry errors.", @@ -45,7 +44,6 @@ var ( ) sourceErrorsTotal = metrics.NewCounterWithOpts( prometheus.CounterOpts{ - Namespace: "external_dns", Subsystem: "source", Name: "errors_total", Help: "Number of Source errors.", @@ -53,7 +51,6 @@ var ( ) sourceEndpointsTotal = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "source", Name: "endpoints_total", Help: "Number of Endpoints in all sources", @@ -61,7 +58,6 @@ var ( ) registryEndpointsTotal = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "registry", Name: "endpoints_total", Help: "Number of Endpoints in the registry", @@ -69,7 +65,6 @@ var ( ) lastSyncTimestamp = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "controller", Name: "last_sync_timestamp_seconds", Help: "Timestamp of last successful sync with the DNS provider", @@ -77,7 +72,6 @@ var ( ) lastReconcileTimestamp = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "controller", Name: "last_reconcile_timestamp_seconds", Help: "Timestamp of last attempted sync with the DNS provider", @@ -85,7 +79,6 @@ var ( ) controllerNoChangesTotal = metrics.NewCounterWithOpts( prometheus.CounterOpts{ - Namespace: "external_dns", Subsystem: "controller", Name: "no_op_runs_total", Help: "Number of reconcile loops ending up with no changes on the DNS provider side.", @@ -108,7 +101,6 @@ var ( registryRecords = metrics.NewGaugedVectorOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "registry", Name: "records", Help: "Number of registry records partitioned by label name (vector).", @@ -118,7 +110,6 @@ var ( sourceRecords = metrics.NewGaugedVectorOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "source", Name: "records", Help: "Number of source records partitioned by label name (vector).", @@ -128,7 +119,6 @@ var ( verifiedRecords = metrics.NewGaugedVectorOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "controller", Name: "verified_records", Help: "Number of DNS records that exists both in source and registry (vector).", @@ -138,7 +128,6 @@ var ( consecutiveSoftErrors = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "controller", Name: "consecutive_soft_errors", Help: "Number of consecutive soft errors in reconciliation loop.", diff --git a/docs/monitoring/metrics.md b/docs/monitoring/metrics.md index c80d39333..3da6d52af 100644 --- a/docs/monitoring/metrics.md +++ b/docs/monitoring/metrics.md @@ -20,6 +20,7 @@ curl https://localhost:7979/metrics | Name | Metric Type | Subsystem | Help | |:---------------------------------|:------------|:------------|:------------------------------------------------------| +| build_info | Gauge | | A metric with a constant '1' value labeled with 'version' and 'revision' of external_dns and the 'go_version', 'os' and the 'arch' used the build. | | consecutive_soft_errors | Gauge | controller | Number of consecutive soft errors in reconciliation loop. | | last_reconcile_timestamp_seconds | Gauge | controller | Timestamp of last attempted sync with the DNS provider | | last_sync_timestamp_seconds | Gauge | controller | Timestamp of last successful sync with the DNS provider | diff --git a/internal/gen/docs/metrics/main_test.go b/internal/gen/docs/metrics/main_test.go index a6d1efc27..bea1d75e8 100644 --- a/internal/gen/docs/metrics/main_test.go +++ b/internal/gen/docs/metrics/main_test.go @@ -37,7 +37,7 @@ func TestComputeMetrics(t *testing.T) { t.Errorf("Expected not empty metrics registry, got %d", len(reg.Metrics)) } - assert.Len(t, reg.Metrics, 19) + assert.Len(t, reg.Metrics, 20) } func TestGenerateMarkdownTableRenderer(t *testing.T) { diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index d65e172ea..73e6ea5d1 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -17,25 +17,44 @@ limitations under the License. package metrics import ( - "runtime" + "fmt" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/version" log "github.com/sirupsen/logrus" cfg "sigs.k8s.io/external-dns/pkg/apis/externaldns" ) +const ( + Namespace = "external_dns" +) + var ( RegisterMetric = NewMetricsRegister() ) +func init() { + RegisterMetric.MustRegister(NewGaugeFuncMetric(prometheus.GaugeOpts{ + Namespace: Namespace, + Name: "build_info", + Help: fmt.Sprintf( + "A metric with a constant '1' value labeled with 'version' and 'revision' of %s and the 'go_version', 'os' and the 'arch' used the build.", + Namespace, + ), + ConstLabels: prometheus.Labels{ + "version": cfg.Version, + "revision": version.GetRevision(), + "go_version": version.GoVersion, + "os": version.GoOS, + "arch": version.GoArch, + }, + })) +} + func NewMetricsRegister() *MetricRegistry { reg := prometheus.WrapRegistererWith( - prometheus.Labels{ - "version": cfg.Version, - "arch": runtime.GOARCH, - "go_version": runtime.Version(), - }, + prometheus.Labels{}, prometheus.DefaultRegisterer) return &MetricRegistry{ Registerer: reg, @@ -54,7 +73,7 @@ func NewMetricsRegister() *MetricRegistry { // } func (m *MetricRegistry) MustRegister(cs IMetric) { switch v := cs.(type) { - case CounterMetric, GaugeMetric, CounterVecMetric, GaugeVecMetric: + case CounterMetric, GaugeMetric, CounterVecMetric, GaugeVecMetric, GaugeFuncMetric: if _, exists := m.mName[cs.Get().FQDN]; exists { return } else { @@ -70,6 +89,8 @@ func (m *MetricRegistry) MustRegister(cs IMetric) { m.Registerer.MustRegister(metric.Gauge) case CounterVecMetric: m.Registerer.MustRegister(metric.CounterVec) + case GaugeFuncMetric: + m.Registerer.MustRegister(metric.GaugeFunc) } log.Debugf("Register metric: %s", cs.Get().FQDN) default: diff --git a/pkg/metrics/models.go b/pkg/metrics/models.go index 1aaf708e7..8c8fb4d4f 100644 --- a/pkg/metrics/models.go +++ b/pkg/metrics/models.go @@ -88,6 +88,7 @@ func (g GaugeVecMetric) SetWithLabels(value float64, lvs ...string) { } func NewGaugeWithOpts(opts prometheus.GaugeOpts) GaugeMetric { + opts.Namespace = Namespace return GaugeMetric{ Metric: Metric{ Type: "gauge", @@ -104,6 +105,7 @@ func NewGaugeWithOpts(opts prometheus.GaugeOpts) GaugeMetric { // NewGaugedVectorOpts creates a new GaugeVec based on the provided GaugeOpts and // partitioned by the given label names. func NewGaugedVectorOpts(opts prometheus.GaugeOpts, labelNames []string) GaugeVecMetric { + opts.Namespace = Namespace return GaugeVecMetric{ Metric: Metric{ Type: "gauge", @@ -118,6 +120,7 @@ func NewGaugedVectorOpts(opts prometheus.GaugeOpts, labelNames []string) GaugeVe } func NewCounterWithOpts(opts prometheus.CounterOpts) CounterMetric { + opts.Namespace = Namespace return CounterMetric{ Metric: Metric{ Type: "counter", @@ -132,6 +135,7 @@ func NewCounterWithOpts(opts prometheus.CounterOpts) CounterMetric { } func NewCounterVecWithOpts(opts prometheus.CounterOpts, labelNames []string) CounterVecMetric { + opts.Namespace = Namespace return CounterVecMetric{ Metric: Metric{ Type: "counter", @@ -144,3 +148,31 @@ func NewCounterVecWithOpts(opts prometheus.CounterOpts, labelNames []string) Cou CounterVec: prometheus.NewCounterVec(opts, labelNames), } } + +type GaugeFuncMetric struct { + Metric + GaugeFunc prometheus.GaugeFunc +} + +func (g GaugeFuncMetric) Get() *Metric { + return &g.Metric +} + +func NewGaugeFuncMetric(opts prometheus.GaugeOpts) GaugeFuncMetric { + return GaugeFuncMetric{ + Metric: Metric{ + Type: "gauge", + Name: opts.Name, + FQDN: func() string { + if opts.Subsystem != "" { + return fmt.Sprintf("%s_%s", opts.Subsystem, opts.Name) + } + return opts.Name + }(), + Namespace: opts.Namespace, + Subsystem: opts.Subsystem, + Help: opts.Help, + }, + GaugeFunc: prometheus.NewGaugeFunc(opts, func() float64 { return 1 }), + } +} diff --git a/pkg/metrics/models_test.go b/pkg/metrics/models_test.go index 70f50ea1a..738f9f6ed 100644 --- a/pkg/metrics/models_test.go +++ b/pkg/metrics/models_test.go @@ -17,18 +17,17 @@ limitations under the License. package metrics import ( + "reflect" "testing" - dto "github.com/prometheus/client_model/go" - "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" ) func TestNewGaugeWithOpts(t *testing.T) { opts := prometheus.GaugeOpts{ Name: "test_gauge", - Namespace: "test_namespace", Subsystem: "test_subsystem", Help: "This is a test gauge", } @@ -37,7 +36,7 @@ func TestNewGaugeWithOpts(t *testing.T) { assert.Equal(t, "gauge", gaugeMetric.Type) assert.Equal(t, "test_gauge", gaugeMetric.Name) - assert.Equal(t, "test_namespace", gaugeMetric.Namespace) + assert.Equal(t, Namespace, gaugeMetric.Namespace) assert.Equal(t, "test_subsystem", gaugeMetric.Subsystem) assert.Equal(t, "This is a test gauge", gaugeMetric.Help) assert.Equal(t, "test_subsystem_test_gauge", gaugeMetric.FQDN) @@ -47,7 +46,6 @@ func TestNewGaugeWithOpts(t *testing.T) { func TestNewCounterWithOpts(t *testing.T) { opts := prometheus.CounterOpts{ Name: "test_counter", - Namespace: "test_namespace", Subsystem: "test_subsystem", Help: "This is a test counter", } @@ -56,7 +54,7 @@ func TestNewCounterWithOpts(t *testing.T) { assert.Equal(t, "counter", counterMetric.Type) assert.Equal(t, "test_counter", counterMetric.Name) - assert.Equal(t, "test_namespace", counterMetric.Namespace) + assert.Equal(t, Namespace, counterMetric.Namespace) assert.Equal(t, "test_subsystem", counterMetric.Subsystem) assert.Equal(t, "This is a test counter", counterMetric.Help) assert.Equal(t, "test_subsystem_test_counter", counterMetric.FQDN) @@ -77,7 +75,7 @@ func TestNewCounterVecWithOpts(t *testing.T) { assert.Equal(t, "counter", counterVecMetric.Type) assert.Equal(t, "test_counter_vec", counterVecMetric.Name) - assert.Equal(t, "test_namespace", counterVecMetric.Namespace) + assert.Equal(t, Namespace, counterVecMetric.Namespace) assert.Equal(t, "test_subsystem", counterVecMetric.Subsystem) assert.Equal(t, "This is a test counter vector", counterVecMetric.Help) assert.Equal(t, "test_subsystem_test_counter_vec", counterVecMetric.FQDN) @@ -113,3 +111,20 @@ func TestGaugeV_SetWithLabels(t *testing.T) { assert.Len(t, m.Label, 2) } + +func TestNewBuildInfoCollector(t *testing.T) { + metric := NewGaugeFuncMetric(prometheus.GaugeOpts{ + Namespace: Namespace, + Name: "build_info", + ConstLabels: prometheus.Labels{ + "version": "0.0.1", + "goversion": "1.24", + "arch": "arm64", + }, + }) + + desc := metric.GaugeFunc.Desc() + + assert.Equal(t, "external_dns_build_info", reflect.ValueOf(desc).Elem().FieldByName("fqName").String()) + assert.Contains(t, desc.String(), "version=\"0.0.1\"") +} diff --git a/provider/cached_provider.go b/provider/cached_provider.go index 8f86d704c..ff9d2d49b 100644 --- a/provider/cached_provider.go +++ b/provider/cached_provider.go @@ -31,7 +31,6 @@ import ( var ( cachedRecordsCallsTotal = metrics.NewCounterVecWithOpts( prometheus.CounterOpts{ - Namespace: "external_dns", Subsystem: "provider", Name: "cache_records_calls", Help: "Number of calls to the provider cache Records list.", @@ -42,7 +41,6 @@ var ( ) cachedApplyChangesCallsTotal = metrics.NewCounterWithOpts( prometheus.CounterOpts{ - Namespace: "external_dns", Subsystem: "provider", Name: "cache_apply_changes_calls", Help: "Number of calls to the provider cache ApplyChanges.", diff --git a/provider/webhook/webhook.go b/provider/webhook/webhook.go index 7e5da1187..787ace17f 100644 --- a/provider/webhook/webhook.go +++ b/provider/webhook/webhook.go @@ -43,7 +43,6 @@ const ( var ( recordsErrorsGauge = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "webhook_provider", Name: "records_errors_total", Help: "Errors with Records method", @@ -51,7 +50,6 @@ var ( ) recordsRequestsGauge = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "webhook_provider", Name: "records_requests_total", Help: "Requests with Records method", @@ -59,7 +57,6 @@ var ( ) applyChangesErrorsGauge = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "webhook_provider", Name: "applychanges_errors_total", Help: "Errors with ApplyChanges method", @@ -67,7 +64,6 @@ var ( ) applyChangesRequestsGauge = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "webhook_provider", Name: "applychanges_requests_total", Help: "Requests with ApplyChanges method", @@ -75,7 +71,6 @@ var ( ) adjustEndpointsErrorsGauge = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "webhook_provider", Name: "adjustendpoints_errors_total", Help: "Errors with AdjustEndpoints method", @@ -83,7 +78,6 @@ var ( ) adjustEndpointsRequestsGauge = metrics.NewGaugeWithOpts( prometheus.GaugeOpts{ - Namespace: "external_dns", Subsystem: "webhook_provider", Name: "adjustendpoints_requests_total", Help: "Requests with AdjustEndpoints method",