Skip to content

Commit c17bdf8

Browse files
refactor: key QuotaUsageRatio metric by namespace, implement label cleanup, and update Grafana dashboard queries to support filtering
Signed-off-by: Ankit Kr. Chowdhury <rakesh856100@gmail.com>
1 parent f478e28 commit c17bdf8

5 files changed

Lines changed: 67 additions & 42 deletions

File tree

‎config/grafana/dashboard-configmap.yaml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ data:
4343
"datasource": "Prometheus",
4444
"targets": [
4545
{
46-
"expr": "sum(rate(agentrax_reconcile_duration_seconds_count[5m])) by (tenant)",
46+
"expr": "sum(rate(agentrax_reconcile_duration_seconds_count{tenant=~\"$tenant\"}[5m])) by (tenant)",
4747
"legendFormat": "{{ tenant }}"
4848
}
4949
],
@@ -82,11 +82,11 @@ data:
8282
"datasource": "Prometheus",
8383
"targets": [
8484
{
85-
"expr": "histogram_quantile(0.99, sum(rate(agentrax_reconcile_duration_seconds_bucket[5m])) by (le, tenant))",
85+
"expr": "histogram_quantile(0.99, sum(rate(agentrax_reconcile_duration_seconds_bucket{tenant=~\"$tenant\"}[5m])) by (le, tenant))",
8686
"legendFormat": "P99 {{ tenant }}"
8787
},
8888
{
89-
"expr": "histogram_quantile(0.50, sum(rate(agentrax_reconcile_duration_seconds_bucket[5m])) by (le, tenant))",
89+
"expr": "histogram_quantile(0.50, sum(rate(agentrax_reconcile_duration_seconds_bucket{tenant=~\"$tenant\"}[5m])) by (le, tenant))",
9090
"legendFormat": "P50 {{ tenant }}"
9191
}
9292
],

‎internal/controller/tenantquota_controller.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func (r *TenantQuotaReconciler) Reconcile(ctx context.Context, req ctrl.Request)
5858
tq := &agentraxv1alpha1.TenantQuota{}
5959
if err := r.Get(ctx, req.NamespacedName, tq); err != nil {
6060
if apierrors.IsNotFound(err) {
61+
observability.QuotaUsageRatio.DeleteLabelValues(req.Namespace)
6162
return ctrl.Result{}, nil
6263
}
6364
return ctrl.Result{}, fmt.Errorf("fetching TenantQuota: %w", err)
@@ -90,6 +91,7 @@ func (r *TenantQuotaReconciler) Reconcile(ctx context.Context, req ctrl.Request)
9091
latest := &agentraxv1alpha1.TenantQuota{}
9192
if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
9293
if apierrors.IsNotFound(err) {
94+
observability.QuotaUsageRatio.DeleteLabelValues(req.Namespace)
9395
return ctrl.Result{}, nil
9496
}
9597
return ctrl.Result{}, fmt.Errorf("re-fetching TenantQuota for status update: %w", err)
@@ -106,9 +108,9 @@ func (r *TenantQuotaReconciler) Reconcile(ctx context.Context, req ctrl.Request)
106108
// Guard against divide-by-zero: if MaxTotalReplicas is zero the ratio is 0.
107109
if latest.Spec.MaxTotalReplicas > 0 {
108110
ratio := float64(usage.UsedTotalReplicas) / float64(latest.Spec.MaxTotalReplicas)
109-
observability.QuotaUsageRatio.WithLabelValues(latest.Name).Set(ratio)
111+
observability.QuotaUsageRatio.WithLabelValues(latest.Namespace).Set(ratio)
110112
} else {
111-
observability.QuotaUsageRatio.WithLabelValues(latest.Name).Set(0)
113+
observability.QuotaUsageRatio.WithLabelValues(latest.Namespace).Set(0)
112114
}
113115

114116
// 5. Set or clear the OverQuota condition based on whether usage exceeds spec.

‎internal/controller/tenantquota_controller_test.go‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import (
3030
"sigs.k8s.io/controller-runtime/pkg/client"
3131

3232
agentraxv1alpha1 "github.com/gitcommitankit/agentrax/api/v1alpha1"
33+
"github.com/gitcommitankit/agentrax/internal/observability"
34+
"github.com/prometheus/client_golang/prometheus/testutil"
3335
)
3436

3537
var _ = Describe("TenantQuota Controller", func() {
@@ -155,6 +157,31 @@ var _ = Describe("TenantQuota Controller", func() {
155157
g.Expect(tqFetched.Status.UsedAgents).To(BeNumerically("==", 0))
156158
}, timeout, interval).Should(Succeed())
157159
})
160+
161+
It("updates and cleans up QuotaUsageRatio metric keyed by tenant namespace", func() {
162+
tq := makeTQ("tq-metric", tqNS, 6, 4, 10, 6)
163+
Expect(k8sClient.Create(ctx, tq)).To(Succeed())
164+
165+
ad := makeBasicAD("ad-metric-1", tqNS, "tq-metric", 2)
166+
Expect(k8sClient.Create(ctx, ad)).To(Succeed())
167+
168+
Eventually(func(g Gomega) {
169+
val := testutil.ToFloat64(observability.QuotaUsageRatio.WithLabelValues(tqNS))
170+
g.Expect(val).To(BeNumerically("~", 0.2, 1e-4))
171+
}, timeout, interval).Should(Succeed())
172+
173+
// Deleting the TenantQuota should trigger reconcile NotFound path and remove the series.
174+
Expect(k8sClient.Delete(ctx, tq)).To(Succeed())
175+
Eventually(func() bool {
176+
err := k8sClient.Get(ctx, namespacedName("tq-metric", tqNS), &agentraxv1alpha1.TenantQuota{})
177+
return apierrors.IsNotFound(err)
178+
}, timeout, interval).Should(BeTrue())
179+
180+
Eventually(func(g Gomega) {
181+
// DeleteLabelValues returns false when the series is already gone.
182+
g.Expect(observability.QuotaUsageRatio.DeleteLabelValues(tqNS)).To(BeFalse())
183+
}, timeout, interval).Should(Succeed())
184+
})
158185
})
159186

160187
// ── OverQuota condition ───────────────────────────────────────────────────

‎internal/observability/metrics.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var ReconcileDuration = prometheus.NewHistogramVec(
2525
// QuotaUsageRatio tracks the current replica usage as a fraction of maxTotalReplicas.
2626
// A value of 1.0 means the tenant is at the hard replica cap.
2727
// Labels:
28-
// - tenant: the TenantQuota name (and namespace)
28+
// - tenant: the Kubernetes namespace (which maps 1:1 to a tenant in Agentrax)
2929
var QuotaUsageRatio = prometheus.NewGaugeVec(
3030
prometheus.GaugeOpts{
3131
Name: "agentrax_tenant_quota_usage_ratio",
Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package observability_test
22

33
import (
4+
"fmt"
45
"strings"
56
"testing"
67

@@ -16,39 +17,36 @@ import (
1617
// observations and that the histogram is correctly exported via text format.
1718
func TestReconcileDurationObserve(t *testing.T) {
1819
reg := prometheus.NewRegistry()
20+
observability.ReconcileDuration.Reset()
21+
reg.MustRegister(observability.ReconcileDuration)
1922

20-
hist := prometheus.NewHistogramVec(
21-
prometheus.HistogramOpts{
22-
Name: "agentrax_reconcile_duration_seconds",
23-
Help: "Duration of AgentDeployment reconcile loops in seconds.",
24-
Buckets: prometheus.DefBuckets,
25-
},
26-
[]string{"controller", "tenant"},
27-
)
28-
reg.MustRegister(hist)
23+
observability.ReconcileDuration.WithLabelValues("agentdeployment", "tenant-test").Observe(0.5)
24+
observability.ReconcileDuration.WithLabelValues("agentdeployment", "tenant-test").Observe(1.5)
2925

30-
hist.WithLabelValues("agentdeployment", "tenant-test").Observe(0.5)
31-
hist.WithLabelValues("agentdeployment", "tenant-test").Observe(1.5)
32-
33-
// Verify via text format: sample_count should be 2.
3426
expected := `
3527
# HELP agentrax_reconcile_duration_seconds Duration of AgentDeployment reconcile loops in seconds.
3628
# TYPE agentrax_reconcile_duration_seconds histogram
29+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="0.005"} 0
30+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="0.01"} 0
31+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="0.025"} 0
32+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="0.05"} 0
33+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="0.1"} 0
34+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="0.25"} 0
35+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="0.5"} 1
36+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="1"} 1
37+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="2.5"} 2
38+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="5"} 2
39+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="10"} 2
40+
agentrax_reconcile_duration_seconds_bucket{controller="agentdeployment",tenant="tenant-test",le="+Inf"} 2
41+
agentrax_reconcile_duration_seconds_sum{controller="agentdeployment",tenant="tenant-test"} 2
42+
agentrax_reconcile_duration_seconds_count{controller="agentdeployment",tenant="tenant-test"} 2
3743
`
38-
err := testutil.GatherAndCompare(reg, strings.NewReader(expected),
39-
"agentrax_reconcile_duration_seconds")
40-
// We only check prefix presence, not exact bucket values — a full comparison
41-
// would require listing all bucket boundaries. Use Count instead.
42-
_ = err
44+
err := testutil.GatherAndCompare(reg, strings.NewReader(expected), "agentrax_reconcile_duration_seconds")
45+
require.NoError(t, err)
4346

4447
count, err := testutil.GatherAndCount(reg, "agentrax_reconcile_duration_seconds")
4548
require.NoError(t, err)
46-
// GatherAndCount returns the number of MetricFamily + all their metrics
47-
// (buckets + sum + count); assert > 0 to prove the histogram emitted output.
4849
assert.Greater(t, count, 0, "expected histogram to produce at least one metric series")
49-
50-
// Verify the package-level var is registered and usable.
51-
_ = observability.ReconcileDuration
5250
}
5351

5452
// TestQuotaUsageRatioSet verifies that QuotaUsageRatio records the expected
@@ -68,22 +66,20 @@ func TestQuotaUsageRatioSet(t *testing.T) {
6866
for _, tc := range tests {
6967
t.Run(tc.name, func(t *testing.T) {
7068
reg := prometheus.NewRegistry()
71-
gauge := prometheus.NewGaugeVec(
72-
prometheus.GaugeOpts{
73-
Name: "agentrax_tenant_quota_usage_ratio",
74-
Help: "Current replica usage as a fraction of maxTotalReplicas.",
75-
},
76-
[]string{"tenant"},
77-
)
78-
reg.MustRegister(gauge)
69+
observability.QuotaUsageRatio.Reset()
70+
reg.MustRegister(observability.QuotaUsageRatio)
7971

80-
gauge.WithLabelValues(tc.tenant).Set(tc.value)
72+
observability.QuotaUsageRatio.WithLabelValues(tc.tenant).Set(tc.value)
8173

82-
got := testutil.ToFloat64(gauge.WithLabelValues(tc.tenant))
74+
expected := fmt.Sprintf(`
75+
# HELP agentrax_tenant_quota_usage_ratio Current replica usage as a fraction of maxTotalReplicas (0.0–1.0) per tenant.
76+
# TYPE agentrax_tenant_quota_usage_ratio gauge
77+
agentrax_tenant_quota_usage_ratio{tenant="%s"} %g
78+
`, tc.tenant, tc.value)
79+
require.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(expected), "agentrax_tenant_quota_usage_ratio"))
80+
81+
got := testutil.ToFloat64(observability.QuotaUsageRatio.WithLabelValues(tc.tenant))
8382
assert.InDelta(t, tc.expected, got, 1e-9)
8483
})
8584
}
86-
87-
// Verify the package-level var is registered and usable.
88-
_ = observability.QuotaUsageRatio
8985
}

0 commit comments

Comments
 (0)