Skip to content

Commit d3242c8

Browse files
committed
feat: add sandbox_creation_latency metric
Adds a new histogram metric 'sandbox_creation_latency_seconds' to track the time it takes for a Sandbox to become Ready after it has been created. This metric is recorded in the sandbox controller when the Sandbox's Ready condition transitions to True. A unit test is also added to verify that the metric is recorded correctly.
1 parent e568f6c commit d3242c8

File tree

3 files changed

+96
-2
lines changed

3 files changed

+96
-2
lines changed

controllers/sandbox_controller.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"reflect"
2323
"time"
2424

25+
"github.com/prometheus/client_golang/prometheus"
2526
corev1 "k8s.io/api/core/v1"
2627
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2728
"k8s.io/apimachinery/pkg/api/meta"
@@ -35,6 +36,7 @@ import (
3536
"sigs.k8s.io/controller-runtime/pkg/client"
3637
"sigs.k8s.io/controller-runtime/pkg/handler"
3738
"sigs.k8s.io/controller-runtime/pkg/log"
39+
"sigs.k8s.io/controller-runtime/pkg/metrics"
3840
"sigs.k8s.io/controller-runtime/pkg/predicate"
3941

4042
sandboxv1alpha1 "sigs.k8s.io/agent-sandbox/api/v1alpha1"
@@ -48,11 +50,19 @@ const (
4850
var (
4951
// Scheme for use by sandbox controllers. Registers required types for client.
5052
Scheme = runtime.NewScheme()
53+
54+
sandboxCreationLatency = prometheus.NewHistogram(
55+
prometheus.HistogramOpts{
56+
Name: "sandbox_creation_latency_seconds",
57+
Help: "Time taken from sandbox creation to sandbox being ready",
58+
},
59+
)
5160
)
5261

5362
func init() {
5463
utilruntime.Must(clientgoscheme.AddToScheme(Scheme))
5564
utilruntime.Must(sandboxv1alpha1.AddToScheme(Scheme))
65+
metrics.Registry.MustRegister(sandboxCreationLatency)
5666
}
5767

5868
// SandboxReconciler reconciles a Sandbox object
@@ -225,6 +235,16 @@ func (r *SandboxReconciler) updateStatus(ctx context.Context, oldStatus *sandbox
225235
return err
226236
}
227237

238+
// record metric if we have just become ready
239+
oldReadyCondition := meta.FindStatusCondition(oldStatus.Conditions, string(sandboxv1alpha1.SandboxConditionReady))
240+
newReadyCondition := meta.FindStatusCondition(sandbox.Status.Conditions, string(sandboxv1alpha1.SandboxConditionReady))
241+
242+
if newReadyCondition != nil && newReadyCondition.Status == metav1.ConditionTrue && (oldReadyCondition == nil || oldReadyCondition.Status != metav1.ConditionTrue) {
243+
latency := time.Since(sandbox.CreationTimestamp.Time)
244+
sandboxCreationLatency.Observe(latency.Seconds())
245+
log.Info("recorded sandbox_creation_latency_seconds", "latency", latency)
246+
}
247+
228248
// Surface error
229249
return nil
230250
}

controllers/sandbox_controller_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
package controllers
1616

1717
import (
18+
"context"
1819
"errors"
1920
"testing"
2021
"time"
2122

2223
"github.com/google/go-cmp/cmp"
2324
"github.com/google/go-cmp/cmp/cmpopts"
25+
dto "github.com/prometheus/client_model/go"
2426
"github.com/stretchr/testify/require"
2527
corev1 "k8s.io/api/core/v1"
2628
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -33,6 +35,7 @@ import (
3335
ctrl "sigs.k8s.io/controller-runtime"
3436
"sigs.k8s.io/controller-runtime/pkg/client"
3537
"sigs.k8s.io/controller-runtime/pkg/client/fake"
38+
"sigs.k8s.io/controller-runtime/pkg/metrics"
3639
)
3740

3841
func newFakeClient(initialObjs ...runtime.Object) client.WithWatch {
@@ -649,3 +652,74 @@ func TestSandboxExpiry(t *testing.T) {
649652
})
650653
}
651654
}
655+
656+
func TestReconcileMetric(t *testing.T) {
657+
sandboxName := "sandbox-name"
658+
sandboxNs := "sandbox-ns"
659+
sb := &sandboxv1alpha1.Sandbox{}
660+
sb.Name = sandboxName
661+
sb.Namespace = sandboxNs
662+
sb.Generation = 1
663+
sb.CreationTimestamp = metav1.NewTime(time.Now().Add(-10 * time.Second))
664+
sb.Spec = sandboxv1alpha1.SandboxSpec{
665+
PodTemplate: sandboxv1alpha1.PodTemplate{
666+
Spec: corev1.PodSpec{
667+
Containers: []corev1.Container{
668+
{
669+
Name: "test-container",
670+
},
671+
},
672+
},
673+
},
674+
}
675+
676+
pod := &corev1.Pod{
677+
ObjectMeta: metav1.ObjectMeta{
678+
Name: sandboxName,
679+
Namespace: sandboxNs,
680+
},
681+
Status: corev1.PodStatus{
682+
Phase: corev1.PodRunning,
683+
Conditions: []corev1.PodCondition{
684+
{
685+
Type: corev1.PodReady,
686+
Status: corev1.ConditionTrue,
687+
},
688+
},
689+
},
690+
}
691+
svc := &corev1.Service{
692+
ObjectMeta: metav1.ObjectMeta{
693+
Name: sandboxName,
694+
Namespace: sandboxNs,
695+
},
696+
}
697+
698+
r := SandboxReconciler{
699+
Client: newFakeClient(sb, pod, svc),
700+
Scheme: Scheme,
701+
}
702+
703+
_, err := r.Reconcile(context.Background(), ctrl.Request{
704+
NamespacedName: types.NamespacedName{
705+
Name: sandboxName,
706+
Namespace: sandboxNs,
707+
},
708+
})
709+
require.NoError(t, err)
710+
711+
// Validate metric
712+
metricFamilies, err := metrics.Registry.Gather()
713+
require.NoError(t, err)
714+
var creationLatencyMetric *dto.MetricFamily
715+
for _, mf := range metricFamilies {
716+
if *mf.Name == "sandbox_creation_latency_seconds" {
717+
creationLatencyMetric = mf
718+
break
719+
}
720+
}
721+
require.NotNil(t, creationLatencyMetric)
722+
require.Equal(t, 1, len(creationLatencyMetric.Metric))
723+
require.Equal(t, uint64(1), *creationLatencyMetric.Metric[0].Histogram.SampleCount)
724+
require.Greater(t, *creationLatencyMetric.Metric[0].Histogram.SampleSum, float64(0))
725+
}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.24.4
44

55
require (
66
github.com/google/go-cmp v0.7.0
7+
github.com/prometheus/client_golang v1.23.2
8+
github.com/prometheus/client_model v0.6.2
79
github.com/stretchr/testify v1.11.1
810
k8s.io/api v0.34.1
911
k8s.io/apiextensions-apiserver v0.34.1
@@ -49,8 +51,6 @@ require (
4951
github.com/onsi/ginkgo/v2 v2.23.3 // indirect
5052
github.com/onsi/gomega v1.37.0 // indirect
5153
github.com/pmezard/go-difflib v1.0.0 // indirect
52-
github.com/prometheus/client_golang v1.23.2 // indirect
53-
github.com/prometheus/client_model v0.6.2 // indirect
5454
github.com/prometheus/common v0.67.1 // indirect
5555
github.com/prometheus/procfs v0.17.0 // indirect
5656
github.com/rogpeppe/go-internal v1.14.1 // indirect

0 commit comments

Comments
 (0)