From b14a3467c43c944158b3f0c8b267e4fccd72c0fa Mon Sep 17 00:00:00 2001 From: David Pait Date: Fri, 28 Feb 2025 16:02:32 -0500 Subject: [PATCH] fix(horizontalpodautoscaler): add selector labels where applicable for hpa spec|status target metrics --- .../horizontalpodautoscaler-metrics.md | 6 +-- internal/store/horizontalpodautoscaler.go | 32 ++++++++++++++-- .../store/horizontalpodautoscaler_test.go | 37 +++++++++++++++---- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/docs/metrics/workload/horizontalpodautoscaler-metrics.md b/docs/metrics/workload/horizontalpodautoscaler-metrics.md index 383bdca13f..c3fe059c0f 100644 --- a/docs/metrics/workload/horizontalpodautoscaler-metrics.md +++ b/docs/metrics/workload/horizontalpodautoscaler-metrics.md @@ -1,15 +1,15 @@ # Horizontal Pod Autoscaler Metrics | Metric name | Metric type | Description | Labels/tags | Status | -| ---------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| ---------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------ | | kube_horizontalpodautoscaler_info | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace>
`scaletargetref_api_version`=<hpa-target-api-version>
`scaletargetref_kind`=<hpa-target-kind>
`scaletargetref_name`=<hpa-target-name> | EXPERIMENTAL | | kube_horizontalpodautoscaler_annotations | Gauge | Kubernetes annotations converted to Prometheus labels controlled via [--metric-annotations-allowlist](../../developer/cli-arguments.md) | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace> | EXPERIMENTAL | | kube_horizontalpodautoscaler_labels | Gauge | Kubernetes labels converted to Prometheus labels controlled via [--metric-labels-allowlist](../../developer/cli-arguments.md) | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace> | STABLE | | kube_horizontalpodautoscaler_metadata_generation | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace> | STABLE | | kube_horizontalpodautoscaler_spec_max_replicas | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace> | STABLE | | kube_horizontalpodautoscaler_spec_min_replicas | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace> | STABLE | -| kube_horizontalpodautoscaler_spec_target_metric | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace>
`metric_name`=<metric-name>
`metric_target_type`=<value\|utilization\|average> | EXPERIMENTAL | -| kube_horizontalpodautoscaler_status_target_metric | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace>
`metric_name`=<metric-name>
`metric_target_type`=<value\|utilization\|average> | EXPERIMENTAL | +| kube_horizontalpodautoscaler_spec_target_metric | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace>
`metric_name`=<metric-name>
`metric_target_type`=<value\|utilization\|average>
`selectorLabel_$labelKey`=<labelValue> | EXPERIMENTAL | +| kube_horizontalpodautoscaler_status_target_metric | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace>
`metric_name`=<metric-name>
`metric_target_type`=<value\|utilization\|average>
`selectorLabel_$labelKey`=<labelValue> | EXPERIMENTAL | | kube_horizontalpodautoscaler_status_condition | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace>
`condition`=<hpa-condition>
`status`=<true\|false\|unknown> | STABLE | | kube_horizontalpodautoscaler_status_current_replicas | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace> | STABLE | | kube_horizontalpodautoscaler_status_desired_replicas | Gauge | | `horizontalpodautoscaler`=<hpa-name>
`namespace`=<hpa-namespace> | STABLE | diff --git a/internal/store/horizontalpodautoscaler.go b/internal/store/horizontalpodautoscaler.go index 3994f14c39..7fc493fb10 100644 --- a/internal/store/horizontalpodautoscaler.go +++ b/internal/store/horizontalpodautoscaler.go @@ -194,14 +194,22 @@ func createHPASpecTargetMetric() generator.FamilyGenerator { var metricTarget autoscaling.MetricTarget // The variable maps the type of metric to the corresponding value metricMap := make(map[metricTargetType]float64) + // Object|Pods|External metrics can use the same name for mutltiple metrics. Add SelectorLabels to the metric labels + selectorLabels := make(map[string]string) switch m.Type { case autoscaling.ObjectMetricSourceType: metricName = m.Object.Metric.Name metricTarget = m.Object.Target + if m.Object.Metric.Selector != nil { + selectorLabels = m.Object.Metric.Selector.MatchLabels + } case autoscaling.PodsMetricSourceType: metricName = m.Pods.Metric.Name metricTarget = m.Pods.Target + if m.Pods.Metric.Selector != nil { + selectorLabels = m.Pods.Metric.Selector.MatchLabels + } case autoscaling.ResourceMetricSourceType: metricName = string(m.Resource.Name) metricTarget = m.Resource.Target @@ -211,6 +219,9 @@ func createHPASpecTargetMetric() generator.FamilyGenerator { case autoscaling.ExternalMetricSourceType: metricName = m.External.Metric.Name metricTarget = m.External.Target + if m.External.Metric.Selector != nil { + selectorLabels = m.External.Metric.Selector.MatchLabels + } default: // Skip unsupported metric type continue @@ -227,9 +238,10 @@ func createHPASpecTargetMetric() generator.FamilyGenerator { } for metricTypeIndex, metricValue := range metricMap { + selectorLabelKeys, selectorLabelValues := kubeMapToPrometheusLabels("selectorlabel", selectorLabels) ms = append(ms, &metric.Metric{ - LabelKeys: targetMetricLabels, - LabelValues: []string{metricName, metricTypeIndex.String()}, + LabelKeys: append(targetMetricLabels, selectorLabelKeys...), + LabelValues: append([]string{metricName, metricTypeIndex.String()}, selectorLabelValues...), Value: metricValue, }) } @@ -253,14 +265,22 @@ func createHPAStatusTargetMetric() generator.FamilyGenerator { var currentMetric autoscaling.MetricValueStatus // The variable maps the type of metric to the corresponding value metricMap := make(map[metricTargetType]float64) + // Object|Pods|External metrics can use the same name for mutltiple metrics. Add SelectorLabels to the metric labels + selectorLabels := make(map[string]string) switch m.Type { case autoscaling.ObjectMetricSourceType: metricName = m.Object.Metric.Name currentMetric = m.Object.Current + if m.Object.Metric.Selector != nil { + selectorLabels = m.Object.Metric.Selector.MatchLabels + } case autoscaling.PodsMetricSourceType: metricName = m.Pods.Metric.Name currentMetric = m.Pods.Current + if m.Pods.Metric.Selector != nil { + selectorLabels = m.Pods.Metric.Selector.MatchLabels + } case autoscaling.ResourceMetricSourceType: metricName = string(m.Resource.Name) currentMetric = m.Resource.Current @@ -270,6 +290,9 @@ func createHPAStatusTargetMetric() generator.FamilyGenerator { case autoscaling.ExternalMetricSourceType: metricName = m.External.Metric.Name currentMetric = m.External.Current + if m.External.Metric.Selector != nil { + selectorLabels = m.External.Metric.Selector.MatchLabels + } default: // Skip unsupported metric type continue @@ -286,9 +309,10 @@ func createHPAStatusTargetMetric() generator.FamilyGenerator { } for metricTypeIndex, metricValue := range metricMap { + selectorLabelKeys, selectorLabelValues := kubeMapToPrometheusLabels("selectorlabel", selectorLabels) ms = append(ms, &metric.Metric{ - LabelKeys: targetMetricLabels, - LabelValues: []string{metricName, metricTypeIndex.String()}, + LabelKeys: append(targetMetricLabels, selectorLabelKeys...), + LabelValues: append([]string{metricName, metricTypeIndex.String()}, selectorLabelValues...), Value: metricValue, }) } diff --git a/internal/store/horizontalpodautoscaler_test.go b/internal/store/horizontalpodautoscaler_test.go index 25903fe432..fa4713d0cf 100644 --- a/internal/store/horizontalpodautoscaler_test.go +++ b/internal/store/horizontalpodautoscaler_test.go @@ -79,6 +79,10 @@ func TestHPAStore(t *testing.T) { Object: &autoscaling.ObjectMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "hits", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "foobar", + }}, }, Target: autoscaling.MetricTarget{ Value: resourcePtr(resource.MustParse("10")), @@ -103,6 +107,11 @@ func TestHPAStore(t *testing.T) { Pods: &autoscaling.PodsMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "transactions_processed", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "foobar", + "k8s.io/name": "foobar", + }}, }, Target: autoscaling.MetricTarget{ AverageValue: resourcePtr(resource.MustParse("33")), @@ -150,6 +159,11 @@ func TestHPAStore(t *testing.T) { External: &autoscaling.ExternalMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "sqs_jobs", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "foobar", + "job.1.type": "type1", + }}, }, Target: autoscaling.MetricTarget{ Value: resourcePtr(resource.MustParse("30")), @@ -215,15 +229,15 @@ func TestHPAStore(t *testing.T) { kube_horizontalpodautoscaler_spec_min_replicas{horizontalpodautoscaler="hpa1",namespace="ns1"} 2 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="cpu",metric_target_type="utilization",namespace="ns1"} 80 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="events",metric_target_type="average",namespace="ns1"} 30 - kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="hits",metric_target_type="average",namespace="ns1"} 12 - kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="hits",metric_target_type="value",namespace="ns1"} 10 + kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="hits",metric_target_type="average",namespace="ns1",selectorlabel_app="foobar"} 12 + kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="hits",metric_target_type="value",namespace="ns1",selectorlabel_app="foobar"} 10 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="connections",metric_target_type="average",namespace="ns1"} 0.7 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="connections",metric_target_type="value",namespace="ns1"} 0.5 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="cpu",metric_target_type="utilization",namespace="ns1"} 80 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="memory",metric_target_type="average",namespace="ns1"} 819200 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="memory",metric_target_type="utilization",namespace="ns1"} 80 - kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="sqs_jobs",metric_target_type="value",namespace="ns1"} 30 - kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="transactions_processed",metric_target_type="average",namespace="ns1"} 33 + kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="sqs_jobs",metric_target_type="value",namespace="ns1",selectorlabel_app="foobar",selectorlabel_job_1_type="type1"} 30 + kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="transactions_processed",metric_target_type="average",namespace="ns1",selectorlabel_app="foobar",selectorlabel_k8s_io_name="foobar"} 33 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa1",metric_name="cpu",metric_target_type="average",namespace="ns1"} 0.007 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa1",metric_name="cpu",metric_target_type="utilization",namespace="ns1"} 80 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa1",metric_name="memory",metric_target_type="average",namespace="ns1"} 2.6335914666e+07 @@ -293,6 +307,10 @@ func TestHPAStore(t *testing.T) { External: &autoscaling.ExternalMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "traefik_backend_requests_per_second", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "foobar", + }}, }, Target: autoscaling.MetricTarget{ Value: resourcePtr(resource.MustParse("100")), @@ -363,6 +381,11 @@ func TestHPAStore(t *testing.T) { External: &autoscaling.ExternalMetricStatus{ Metric: autoscaling.MetricIdentifier{ Name: "traefik_backend_requests_per_second", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "foobar", + "app-type": "traefik", + }}, }, Current: autoscaling.MetricValueStatus{ Value: resourcePtr(resource.MustParse("0")), @@ -393,15 +416,15 @@ func TestHPAStore(t *testing.T) { kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa2",metric_name="cpu",metric_target_type="utilization",namespace="ns1"} 80 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa2",metric_name="memory",metric_target_type="utilization",namespace="ns1"} 75 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_errors_per_second",metric_target_type="value",namespace="ns1"} 100 - kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_requests_per_second",metric_target_type="value",namespace="ns1"} 100 + kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_requests_per_second",metric_target_type="value",namespace="ns1",selectorlabel_app="foobar"} 100 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="memory",metric_target_type="average",namespace="ns1"} 8.47775744e+08 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="memory",metric_target_type="utilization",namespace="ns1"} 28 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="cpu",metric_target_type="average",namespace="ns1"} 0.062 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="cpu",metric_target_type="utilization",namespace="ns1"} 6 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="cpu",metric_target_type="average",namespace="ns1"} 0.08 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="cpu",metric_target_type="utilization",namespace="ns1"} 10 - kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_requests_per_second",metric_target_type="value",namespace="ns1"} 0 - kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_requests_per_second",metric_target_type="average",namespace="ns1"} 2.9 + kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_requests_per_second",metric_target_type="value",namespace="ns1",selectorlabel_app="foobar",selectorlabel_app_type="traefik"} 0 + kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_requests_per_second",metric_target_type="average",namespace="ns1",selectorlabel_app="foobar",selectorlabel_app_type="traefik"} 2.9 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_errors_per_second",metric_target_type="value",namespace="ns1"} 0 kube_horizontalpodautoscaler_status_condition{condition="AbleToScale",horizontalpodautoscaler="hpa2",namespace="ns1",status="false"} 0 kube_horizontalpodautoscaler_status_condition{condition="AbleToScale",horizontalpodautoscaler="hpa2",namespace="ns1",status="true"} 1