diff --git a/CHANGELOG.md b/CHANGELOG.md index f26aa0ec8c5..8b4637da0c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio - **Pulsar Scaler**: Improve error messages for unsuccessful connections ([#4563](https://github.com/kedacore/keda/issues/4563)) - **Security:** Enable secret scanning in GitHub repo - **RabbitMQ Scaler**: Add support for `unsafeSsl` in trigger metadata ([#4448](https://github.com/kedacore/keda/issues/4448)) +- **Prometheus Metrics**: Add new metric with KEDA build info ([#4647](https://github.com/kedacore/keda/issues/4647)) ### Fixes diff --git a/pkg/prommetrics/prommetrics.go b/pkg/prommetrics/prommetrics.go index 613d29aaa4c..985930ef2d9 100644 --- a/pkg/prommetrics/prommetrics.go +++ b/pkg/prommetrics/prommetrics.go @@ -17,11 +17,14 @@ limitations under the License. package prommetrics import ( + "runtime" "strconv" "github.com/prometheus/client_golang/prometheus" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/metrics" + + "github.com/kedacore/keda/v2/version" ) var log = logf.Log.WithName("prometheus_server") @@ -36,7 +39,15 @@ const ( ) var ( - metricLabels = []string{"namespace", "metric", "scaledObject", "scaler", "scalerIndex"} + metricLabels = []string{"namespace", "metric", "scaledObject", "scaler", "scalerIndex"} + buildInfo = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: DefaultPromMetricsNamespace, + Name: "build_info", + Help: "A metric with a constant '1' value labeled by version, git_commit and goversion from which KEDA was built.", + }, + []string{"version", "git_commit", "goversion", "goos", "goarch"}, + ) scalerErrorsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: DefaultPromMetricsNamespace, @@ -121,6 +132,9 @@ func init() { metrics.Registry.MustRegister(triggerTotalsGaugeVec) metrics.Registry.MustRegister(crdTotalsGaugeVec) + metrics.Registry.MustRegister(buildInfo) + + RecordBuildInfo() } // RecordScalerMetric create a measurement of the external metric used by the HPA @@ -173,6 +187,11 @@ func RecordScaledObjectError(namespace string, scaledObject string, err error) { } } +// RecordBuildInfo publishes information about KEDA version and runtime info through an info metric (gauge). +func RecordBuildInfo() { + buildInfo.WithLabelValues(version.Version, version.GitCommit, runtime.Version(), runtime.GOOS, runtime.GOARCH).Set(1) +} + func getLabels(namespace string, scaledObject string, scaler string, scalerIndex int, metric string) prometheus.Labels { return prometheus.Labels{"namespace": namespace, "scaledObject": scaledObject, "scaler": scaler, "scalerIndex": strconv.Itoa(scalerIndex), "metric": metric} } diff --git a/tests/sequential/prometheus_metrics/prometheus_metrics_test.go b/tests/sequential/prometheus_metrics/prometheus_metrics_test.go index c3637e5186d..216bfbb0683 100644 --- a/tests/sequential/prometheus_metrics/prometheus_metrics_test.go +++ b/tests/sequential/prometheus_metrics/prometheus_metrics_test.go @@ -4,8 +4,10 @@ package prometheus_metrics_test import ( + "bytes" "context" "fmt" + "os/exec" "strings" "testing" "time" @@ -13,6 +15,7 @@ import ( prommodel "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -581,6 +584,44 @@ func testOperatorMetricValues(t *testing.T, kc *kubernetes.Clientset) { checkTriggerTotalValues(t, families, expectedTriggerTotals) checkCRTotalValues(t, families, expectedCrTotals) + checkBuildInfo(t, families) +} + +func checkBuildInfo(t *testing.T, families map[string]*prommodel.MetricFamily) { + t.Log("--- testing build info metric ---") + + family, ok := families["keda_build_info"] + if !ok { + t.Errorf("metric not available") + return + } + + latestCommit := getLatestCommit(t) + expected := map[string]string{ + "git_commit": latestCommit, + "goos": "linux", + } + + metrics := family.GetMetric() + for _, metric := range metrics { + labels := metric.GetLabel() + for _, labelPair := range labels { + if expectedValue, ok := expected[*labelPair.Name]; ok { + assert.EqualValues(t, expectedValue, *labelPair.Value, "values do not match for label %s", *labelPair.Name) + } + } + assert.EqualValues(t, 1, metric.GetGauge().GetValue()) + } +} + +func getLatestCommit(t *testing.T) string { + cmd := exec.Command("git", "rev-parse", "HEAD") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + require.NoError(t, err) + + return strings.Trim(out.String(), "\n") } func checkTriggerTotalValues(t *testing.T, families map[string]*prommodel.MetricFamily, expected map[string]int) {