feat(metrics): publish build_info metric (#5643)

* feat(metrics): publish build_info metric

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): publish build_info metric

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): publish build_info metric

Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>

* feat(metrics): publish build_info metric

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

---------

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>
This commit is contained in:
Ivan Ka 2025-07-14 20:42:27 +01:00 committed by GitHub
parent faeee1226e
commit b93d1e9abb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 84 additions and 34 deletions

View File

@ -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.",

View File

@ -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 |

View File

@ -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) {

View File

@ -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:

View File

@ -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 }),
}
}

View File

@ -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\"")
}

View File

@ -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.",

View File

@ -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",