From 105ef54bfad3be21cdd5c5eb9fcc1d1299d43936 Mon Sep 17 00:00:00 2001 From: amarkdotdev Date: Tue, 23 Jun 2026 22:51:25 +0300 Subject: [PATCH 1/4] feat(metrics): expose velero_build_info gauge Register a velero_build_info Prometheus metric labeled with version, git commit, tree state, and Go runtime details at server startup. Fixes #2128. Signed-off-by: amarkdotdev --- pkg/cmd/server/server.go | 9 +++++++++ pkg/metrics/metrics.go | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 83627f9d10..9bace1639a 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -23,6 +23,7 @@ import ( "net/http" "net/http/pprof" "os" + "runtime" "strings" "time" @@ -560,6 +561,14 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string }() s.metrics = metrics.NewServerMetrics() s.metrics.RegisterAllMetrics() + s.metrics.RegisterBuildInfo( + buildinfo.Version, + buildinfo.GitSHA, + buildinfo.GitTreeState, + runtime.Version(), + runtime.GOOS, + runtime.GOARCH, + ) // Initialize manual backup metrics s.metrics.InitSchedule("") diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 86d78028c0..81f25f5f63 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -90,6 +90,7 @@ const ( // Each completed job's duration is recorded in the appropriate bucket, allowing // analysis of individual job performance and trending over time. repoMaintenanceDurationSeconds = "repo_maintenance_duration_seconds" + buildInfo = "build_info" // Labels nodeMetricLabel = "node" @@ -393,6 +394,14 @@ func NewServerMetrics() *ServerMetrics { }, []string{repositoryNameLabel}, ), + buildInfo: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: metricNamespace, + Name: buildInfo, + Help: "A metric with a constant '1' value labeled with Velero build information.", + }, + []string{"version", "git_commit", "git_tree_state", "go_version", "goos", "goarch"}, + ), }, } } @@ -502,6 +511,13 @@ func (m *ServerMetrics) RegisterAllMetrics() { } } +// RegisterBuildInfo records static build information for the Velero server. +func (m *ServerMetrics) RegisterBuildInfo(version, gitCommit, gitTreeState, goVersion, goos, goarch string) { + if g, ok := m.metrics[buildInfo].(*prometheus.GaugeVec); ok { + g.WithLabelValues(version, gitCommit, gitTreeState, goVersion, goos, goarch).Set(1) + } +} + // InitSchedule initializes counter metrics of a schedule. func (m *ServerMetrics) InitSchedule(scheduleName string) { if c, ok := m.metrics[backupAttemptTotal].(*prometheus.CounterVec); ok { From 662a28bbd93d22f7de6572f2f004a0ff5b31029b Mon Sep 17 00:00:00 2001 From: amarkdotdev Date: Mon, 29 Jun 2026 07:12:38 +0000 Subject: [PATCH 2/4] Fix runtime import collision and add changelog The build-info metric needs Go's standard `runtime` package, but pkg/cmd/server/server.go already imports `k8s.io/apimachinery/pkg/runtime` under the same name, which broke the build (`runtime` redeclared, `runtime.NewScheme` undefined). Alias the standard library import as `goruntime` and use it for the build-info values, leaving the apimachinery `runtime` untouched. Also add the required changelog entry for this PR. Signed-off-by: amarkdotdev --- changelogs/unreleased/9934-amarkdotdev | 1 + pkg/cmd/server/server.go | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/9934-amarkdotdev diff --git a/changelogs/unreleased/9934-amarkdotdev b/changelogs/unreleased/9934-amarkdotdev new file mode 100644 index 0000000000..aa0b05d133 --- /dev/null +++ b/changelogs/unreleased/9934-amarkdotdev @@ -0,0 +1 @@ +Add velero_build_info metric exposing the Velero version, git SHA, git tree state, and Go runtime details diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 9bace1639a..f63d8053f3 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -23,7 +23,7 @@ import ( "net/http" "net/http/pprof" "os" - "runtime" + goruntime "runtime" "strings" "time" @@ -565,9 +565,9 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string buildinfo.Version, buildinfo.GitSHA, buildinfo.GitTreeState, - runtime.Version(), - runtime.GOOS, - runtime.GOARCH, + goruntime.Version(), + goruntime.GOOS, + goruntime.GOARCH, ) // Initialize manual backup metrics s.metrics.InitSchedule("") From 86f1305efde22e7b6c7e58346279b0b76d3cf316 Mon Sep 17 00:00:00 2001 From: amarkdotdev Date: Thu, 2 Jul 2026 15:08:06 +0300 Subject: [PATCH 3/4] test(metrics): cover RegisterBuildInfo for codecov Signed-off-by: amarkdotdev --- pkg/metrics/metrics_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkg/metrics/metrics_test.go b/pkg/metrics/metrics_test.go index a24f2bf333..cc8ca38b4f 100644 --- a/pkg/metrics/metrics_test.go +++ b/pkg/metrics/metrics_test.go @@ -601,3 +601,35 @@ func getMaintenanceHistogramCount(t *testing.T, vec *prometheus.HistogramVec, re t.Fatalf("Histogram with repository_name label '%s' not found", repositoryName) return 0 } + +func TestRegisterBuildInfo(t *testing.T) { + m := NewServerMetrics() + m.RegisterBuildInfo("v1.13.0", "abc123", "clean", "go1.22.0", "linux", "amd64") + + ch := make(chan prometheus.Metric, 1) + m.metrics[buildInfo].(*prometheus.GaugeVec).Collect(ch) + close(ch) + + var found bool + for metric := range ch { + dtoMetric := &dto.Metric{} + require.NoError(t, metric.Write(dtoMetric)) + if dtoMetric.GetGauge().GetValue() != 1 { + continue + } + labels := map[string]string{} + for _, label := range dtoMetric.Label { + labels[label.GetName()] = label.GetValue() + } + assert.Equal(t, map[string]string{ + "version": "v1.13.0", + "git_commit": "abc123", + "git_tree_state": "clean", + "go_version": "go1.22.0", + "goos": "linux", + "goarch": "amd64", + }, labels) + found = true + } + require.True(t, found, "build_info metric not found") +} From fba4782ea58ff38be9d161df91ef3adb73a7f962 Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 31 Aug 2026 18:00:35 +0300 Subject: [PATCH 4/4] test(server): cover build_info registration for codecov Signed-off-by: Aaron --- pkg/cmd/server/server.go | 20 ++++++++++++-------- pkg/cmd/server/server_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index f63d8053f3..5c6c1d913a 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -561,14 +561,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string }() s.metrics = metrics.NewServerMetrics() s.metrics.RegisterAllMetrics() - s.metrics.RegisterBuildInfo( - buildinfo.Version, - buildinfo.GitSHA, - buildinfo.GitTreeState, - goruntime.Version(), - goruntime.GOOS, - goruntime.GOARCH, - ) + registerBuildInfo(s.metrics) // Initialize manual backup metrics s.metrics.InitSchedule("") @@ -1213,3 +1206,14 @@ func markPodVolumeRestoresCancel(ctx context.Context, client ctrlclient.Client, } } } + +func registerBuildInfo(m *metrics.ServerMetrics) { + m.RegisterBuildInfo( + buildinfo.Version, + buildinfo.GitSHA, + buildinfo.GitTreeState, + goruntime.Version(), + goruntime.GOOS, + goruntime.GOARCH, + ) +} diff --git a/pkg/cmd/server/server_test.go b/pkg/cmd/server/server_test.go index c602f7c9e8..fb881dd54f 100644 --- a/pkg/cmd/server/server_test.go +++ b/pkg/cmd/server/server_test.go @@ -24,6 +24,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + dto "github.com/prometheus/client_model/go" + "github.com/prometheus/client_golang/prometheus" corev1api "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -34,10 +36,12 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" "github.com/vmware-tanzu/velero/pkg/builder" + "github.com/vmware-tanzu/velero/pkg/buildinfo" "github.com/vmware-tanzu/velero/pkg/client/mocks" "github.com/vmware-tanzu/velero/pkg/cmd/server/config" "github.com/vmware-tanzu/velero/pkg/constant" discovery_mocks "github.com/vmware-tanzu/velero/pkg/discovery/mocks" + "github.com/vmware-tanzu/velero/pkg/metrics" velerotest "github.com/vmware-tanzu/velero/pkg/test" "github.com/vmware-tanzu/velero/pkg/uploader" ) @@ -448,3 +452,31 @@ func Test_setDefaultBackupLocation(t *testing.T) { err = setDefaultBackupLocation(t.Context(), c, "velero", "default", logrus.New()) assert.NoError(t, err) } + +func TestRegisterBuildInfo(t *testing.T) { + m := metrics.NewServerMetrics() + m.RegisterAllMetrics() + registerBuildInfo(m) + + ch := make(chan prometheus.Metric, 1) + m.Metrics()["build_info"].(*prometheus.GaugeVec).Collect(ch) + close(ch) + + var found bool + for metric := range ch { + dtoMetric := &dto.Metric{} + require.NoError(t, metric.Write(dtoMetric)) + if dtoMetric.GetGauge().GetValue() != 1 { + continue + } + labels := map[string]string{} + for _, label := range dtoMetric.Label { + labels[label.GetName()] = label.GetValue() + } + assert.Equal(t, buildinfo.Version, labels["version"]) + assert.Equal(t, buildinfo.GitSHA, labels["git_commit"]) + assert.Equal(t, buildinfo.GitTreeState, labels["git_tree_state"]) + found = true + } + require.True(t, found, "build_info metric not found") +}