Skip to content

Commit 57f66b1

Browse files
kumaramit01Amit Kumar
andauthored
Disable metrics for entire GVK (#14)
Co-authored-by: Amit Kumar <[email protected]>
1 parent 9daa7e4 commit 57f66b1

File tree

4 files changed

+237
-8
lines changed

4 files changed

+237
-8
lines changed

pkg/fsm/internal/test/core/controller_suite_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import (
1515
"sigs.k8s.io/controller-runtime/pkg/client"
1616
"sigs.k8s.io/controller-runtime/pkg/manager"
1717

18+
achapi "github.com/reddit/achilles-sdk-api/api"
1819
"github.com/reddit/achilles-sdk/pkg/fsm/metrics"
20+
fsmtypes "github.com/reddit/achilles-sdk/pkg/fsm/types"
1921
internalscheme "github.com/reddit/achilles-sdk/pkg/internal/scheme"
2022
"github.com/reddit/achilles-sdk/pkg/internal/tests"
2123
testv1alpha1 "github.com/reddit/achilles-sdk/pkg/internal/tests/api/test/v1alpha1"
@@ -53,7 +55,13 @@ var _ = BeforeSuite(func() {
5355
Expect(testv1alpha1.AddToScheme(scheme)).ToNot(HaveOccurred())
5456

5557
reg = prometheus.NewRegistry()
56-
metrics := metrics.MustMakeMetrics(scheme, reg)
58+
options := fsmtypes.MetricsOptions{
59+
ConditionTypes: []achapi.ConditionType{
60+
InitialStateConditionType,
61+
},
62+
DisableMetrics: []fsmtypes.AchillesMetrics{},
63+
}
64+
metrics := metrics.MustMakeMetricsWithOptions(scheme, reg, options)
5765

5866
var err error
5967
testEnv, err = test.NewEnvTestBuilder(ctx).

pkg/fsm/metrics/metrics.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"sigs.k8s.io/controller-runtime/pkg/client"
1010

1111
"github.com/reddit/achilles-sdk-api/api"
12+
"github.com/reddit/achilles-sdk/pkg/fsm/types"
1213
"github.com/reddit/achilles-sdk/pkg/meta"
1314
)
1415

@@ -18,8 +19,9 @@ type conditionedObject interface {
1819
}
1920

2021
type Metrics struct {
21-
scheme *runtime.Scheme
22-
sink *Sink
22+
scheme *runtime.Scheme
23+
sink *Sink
24+
options types.MetricsOptions
2325
}
2426

2527
// MustMakeMetrics creates a new Metrics with a new metrics Sink, and the Metrics.Scheme set to that of the given manager.
@@ -33,6 +35,18 @@ func MustMakeMetrics(scheme *runtime.Scheme, registrar prometheus.Registerer) *M
3335
}
3436
}
3537

38+
// MustMakeMetricsWithOptions creates a new Metrics with a new metrics Sink, the Metrics.Scheme set to that of the given manager and MetricsOptions.
39+
func MustMakeMetricsWithOptions(scheme *runtime.Scheme, registrar prometheus.Registerer, options types.MetricsOptions) *Metrics {
40+
metricsRecorder := NewSink()
41+
registrar.MustRegister(metricsRecorder.Collectors()...)
42+
43+
return &Metrics{
44+
scheme: scheme,
45+
sink: metricsRecorder,
46+
options: options,
47+
}
48+
}
49+
3650
// Reset resets all metrics.
3751
func (m *Metrics) Reset() {
3852
m.sink.Reset()
@@ -46,7 +60,7 @@ func (m *Metrics) RecordTrigger(
4660
triggerType string,
4761
controllerName string,
4862
) {
49-
if m.sink == nil {
63+
if m.sink == nil || m.options.IsMetricDisabled(types.AchillesResourceTrigger) {
5064
return
5165
}
5266

@@ -67,6 +81,9 @@ func (m *Metrics) DeleteTrigger(
6781

6882
// RecordReadiness records the meta.ReadyCondition status for the given obj.
6983
func (m *Metrics) RecordReadiness(obj conditionedObject) {
84+
if m.options.IsMetricDisabled(types.AchillesResourceReadiness) {
85+
return
86+
}
7087
m.RecordCondition(obj, api.TypeReady)
7188
}
7289

@@ -77,7 +94,7 @@ func (m *Metrics) DeleteReadiness(obj conditionedObject) {
7794

7895
// RecordCondition records the status of the given conditionType for the given obj.
7996
func (m *Metrics) RecordCondition(obj conditionedObject, conditionType api.ConditionType) {
80-
if m.sink == nil {
97+
if m.sink == nil || m.options.IsMetricDisabled(types.AchillesResourceCondition) {
8198
return
8299
}
83100

@@ -94,7 +111,7 @@ func (m *Metrics) RecordCondition(obj conditionedObject, conditionType api.Condi
94111

95112
// DeleteCondition deletes the status of the given conditionType for the given obj.
96113
func (m *Metrics) DeleteCondition(obj conditionedObject, conditionType api.ConditionType) {
97-
if m.sink == nil {
114+
if m.sink == nil || m.options.IsMetricDisabled(types.AchillesResourceCondition) {
98115
return
99116
}
100117

@@ -110,7 +127,7 @@ func (m *Metrics) DeleteCondition(obj conditionedObject, conditionType api.Condi
110127

111128
// RecordStateDuration records the duration of the state for the given GVK.
112129
func (m *Metrics) RecordStateDuration(gvk schema.GroupVersionKind, state string, duration time.Duration) {
113-
if m.sink == nil {
130+
if m.sink == nil || m.options.IsMetricDisabled(types.AchillesStateDuration) {
114131
return
115132
}
116133

@@ -119,7 +136,7 @@ func (m *Metrics) RecordStateDuration(gvk schema.GroupVersionKind, state string,
119136

120137
// RecordSuspend records status of the object to be 1 if suspended and 0 if unsuspended
121138
func (m *Metrics) RecordSuspend(obj client.Object, suspend bool) {
122-
if m.sink == nil {
139+
if m.sink == nil || m.options.IsMetricDisabled(types.AchillesSuspend) {
123140
return
124141
}
125142

pkg/fsm/metrics/metrics_test.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package metrics
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/prometheus/client_golang/prometheus"
8+
"github.com/prometheus/client_golang/prometheus/testutil"
9+
"github.com/stretchr/testify/assert"
10+
corev1 "k8s.io/api/core/v1"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/runtime"
13+
"k8s.io/apimachinery/pkg/runtime/schema"
14+
types2 "k8s.io/apimachinery/pkg/types"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
17+
"github.com/reddit/achilles-sdk/pkg/fsm/types"
18+
)
19+
20+
func initMetrics(scheme *runtime.Scheme, disableMetrics []types.AchillesMetrics) *Metrics {
21+
scheme.AddKnownTypes(schema.GroupVersion{
22+
Group: "v1.Pod",
23+
Version: "1.0",
24+
}, &corev1.Pod{})
25+
metricOptions := types.MetricsOptions{
26+
DisableMetrics: disableMetrics,
27+
}
28+
return MustMakeMetricsWithOptions(scheme, prometheus.NewRegistry(), metricOptions)
29+
}
30+
31+
func addKnownTypes(scheme *runtime.Scheme, obj client.Object) {
32+
scheme.AddKnownTypes(schema.GroupVersion{
33+
Group: "v1.Pod",
34+
Version: "1.0",
35+
}, obj)
36+
}
37+
38+
func runTest(t *testing.T, name string, obj client.Object, expected int, metric *Metrics, metricName string, c prometheus.Collector, testFunc func()) {
39+
t.Run(name, func(t *testing.T) {
40+
addKnownTypes(runtime.NewScheme(), obj)
41+
testFunc()
42+
count := testutil.CollectAndCount(c, metricName)
43+
assert.Equal(t, expected, count)
44+
})
45+
}
46+
47+
func TestRecordTrigger(t *testing.T) {
48+
scheme := runtime.NewScheme()
49+
metrics := MustMakeMetrics(scheme, prometheus.NewRegistry())
50+
metricsDisabled := initMetrics(scheme, []types.AchillesMetrics{types.AchillesResourceTrigger})
51+
52+
tests := []struct {
53+
name string
54+
obj client.Object
55+
expected int
56+
metric *Metrics
57+
metricName string
58+
collector prometheus.Collector
59+
}{
60+
{
61+
name: "record trigger metric is enabled",
62+
obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}},
63+
expected: 1,
64+
metric: metrics,
65+
metricName: "achilles_trigger",
66+
collector: metrics.sink.triggerCounter,
67+
},
68+
{
69+
name: "record trigger metric is disabled",
70+
obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}},
71+
expected: 0,
72+
metric: metricsDisabled,
73+
metricName: "achilles_trigger",
74+
collector: metricsDisabled.sink.triggerCounter,
75+
},
76+
}
77+
78+
for _, tt := range tests {
79+
runTest(t, tt.name, tt.obj, tt.expected, tt.metric, tt.metricName, tt.collector, func() {
80+
tt.metric.RecordTrigger(schema.GroupVersionKind{
81+
Group: "v1.Pod",
82+
Version: "1.0",
83+
Kind: "v1/Pod",
84+
}, types2.NamespacedName{
85+
Namespace: "default",
86+
Name: "test-pod",
87+
}, "xx", "yy", "dd")
88+
})
89+
}
90+
}
91+
92+
func TestRecordSuspend(t *testing.T) {
93+
scheme := runtime.NewScheme()
94+
metrics := MustMakeMetrics(scheme, prometheus.NewRegistry())
95+
metricsDisabled := initMetrics(scheme, []types.AchillesMetrics{types.AchillesSuspend})
96+
97+
tests := []struct {
98+
name string
99+
obj client.Object
100+
expected int
101+
metric *Metrics
102+
metricName string
103+
collector prometheus.Collector
104+
}{
105+
{
106+
name: "suspended metric is enabled",
107+
obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}},
108+
expected: 1,
109+
metric: metrics,
110+
metricName: "achilles_object_suspended",
111+
collector: metrics.sink.suspendGauge,
112+
},
113+
{
114+
name: "suspended metric is disabled",
115+
obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}},
116+
expected: 0,
117+
metric: metricsDisabled,
118+
metricName: "achilles_object_suspended",
119+
collector: metricsDisabled.sink.suspendGauge,
120+
},
121+
}
122+
123+
for _, tt := range tests {
124+
runTest(t, tt.name, tt.obj, tt.expected, tt.metric, tt.metricName, tt.collector, func() {
125+
tt.metric.RecordSuspend(tt.obj, true)
126+
})
127+
}
128+
}
129+
130+
func TestRecordStateDuration(t *testing.T) {
131+
scheme := runtime.NewScheme()
132+
metrics := MustMakeMetrics(scheme, prometheus.NewRegistry())
133+
metricsDisabled := initMetrics(scheme, []types.AchillesMetrics{types.AchillesStateDuration})
134+
135+
tests := []struct {
136+
name string
137+
obj client.Object
138+
expected int
139+
metric *Metrics
140+
metricName string
141+
collector prometheus.Collector
142+
}{
143+
{
144+
name: "record duration metric is enabled",
145+
obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}},
146+
expected: 1,
147+
metric: metrics,
148+
metricName: "achilles_state_duration_seconds",
149+
collector: metrics.sink.stateDurationHistogram,
150+
},
151+
{
152+
name: "record duration metric is disabled",
153+
obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}},
154+
expected: 0,
155+
metric: metricsDisabled,
156+
metricName: "achilles_state_duration_seconds",
157+
collector: metricsDisabled.sink.stateDurationHistogram,
158+
},
159+
}
160+
161+
for _, tt := range tests {
162+
runTest(t, tt.name, tt.obj, tt.expected, tt.metric, tt.metricName, tt.collector, func() {
163+
tt.metric.RecordStateDuration(schema.GroupVersionKind{
164+
Group: "v1.Pod",
165+
Version: "1.0",
166+
Kind: "v1/Pod",
167+
}, "xxx", time.Second)
168+
})
169+
}
170+
}

pkg/fsm/types/options.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,44 @@ type ReconcilerOptions[T any, Obj types.FSMResource[T]] struct {
2525
MetricsOptions MetricsOptions
2626
}
2727

28+
// AchillesMetrics represents various achilles metrics.
29+
type AchillesMetrics string
30+
31+
// String returns AchillesMetrics as a string.
32+
func (a AchillesMetrics) String() string {
33+
return string(a)
34+
}
35+
36+
// AchillesMetrics type.
37+
const (
38+
// AchillesResourceReadiness represents if the resource is ready or not.
39+
AchillesResourceReadiness = "ResourceReadiness"
40+
// AchillesResourceTrigger trigger for the resource.
41+
AchillesResourceTrigger = "ResourceTrigger"
42+
// AchillesResourceCondition condition of the resource see api.ConditionType.
43+
AchillesResourceCondition = "ResourceCondition"
44+
// AchillesStateDuration duration of the state.
45+
AchillesStateDuration = "StateDuration"
46+
// AchillesSuspend suspend reconciliation
47+
AchillesSuspend = "ResourceSuspend"
48+
)
49+
2850
// MetricsOptions are options for tuning the metrics instrumentation of this reconciler.
2951
type MetricsOptions struct {
3052
// ConditionTypes is a list of additional condition types for which to instrument status condition metrics.
3153
ConditionTypes []api.ConditionType
54+
// DisableMetrics is a list of metrics to be disabled.
55+
DisableMetrics []AchillesMetrics
56+
}
57+
58+
// IsMetricDisabled check if metric is disabled for recording.
59+
func (m *MetricsOptions) IsMetricDisabled(metric AchillesMetrics) bool {
60+
for _, v := range m.DisableMetrics {
61+
if v == metric {
62+
return true
63+
}
64+
}
65+
return false
3266
}
3367

3468
// DefaultCreateFunc is the default CreateFunc invoked if CreateFunc is not specified.

0 commit comments

Comments
 (0)