@@ -28,6 +28,7 @@ import (
28
28
29
29
type Controller [T Object ] struct {
30
30
gvk schema.GroupVersionKind
31
+ additionalMetricLabels []string
31
32
kubeClient client.Client
32
33
eventRecorder record.EventRecorder
33
34
observedConditions sync.Map // map[reconcile.Request]ConditionSet
@@ -51,12 +52,19 @@ type Option struct {
51
52
// - operator_termination_current_time_seconds
52
53
// - operator_termination_duration_seconds
53
54
EmitDeprecatedMetrics bool
55
+ MetricLabels []string
54
56
}
55
57
56
58
func EmitDeprecatedMetrics (o * Option ) {
57
59
o .EmitDeprecatedMetrics = true
58
60
}
59
61
62
+ func WithLabels (labels ... string ) func (* Option ) {
63
+ return func (o * Option ) {
64
+ o .MetricLabels = append (o .MetricLabels , labels ... )
65
+ }
66
+ }
67
+
60
68
func NewController [T Object ](client client.Client , eventRecorder record.EventRecorder , opts ... option.Function [Option ]) * Controller [T ] {
61
69
options := option .Resolve (opts ... )
62
70
obj := reflect .New (reflect .TypeOf (* new (T )).Elem ()).Interface ().(runtime.Object )
@@ -65,15 +73,16 @@ func NewController[T Object](client client.Client, eventRecorder record.EventRec
65
73
66
74
return & Controller [T ]{
67
75
gvk : gvk ,
76
+ additionalMetricLabels : options .MetricLabels ,
68
77
kubeClient : client ,
69
78
eventRecorder : eventRecorder ,
70
79
emitDeprecatedMetrics : options .EmitDeprecatedMetrics ,
71
- ConditionDuration : conditionDurationMetric (strings .ToLower (gvk .Kind )),
72
- ConditionCount : conditionCountMetric (strings .ToLower (gvk .Kind )),
73
- ConditionCurrentStatusSeconds : conditionCurrentStatusSecondsMetric (strings .ToLower (gvk .Kind )),
74
- ConditionTransitionsTotal : conditionTransitionsTotalMetric (strings .ToLower (gvk .Kind )),
75
- TerminationCurrentTimeSeconds : terminationCurrentTimeSecondsMetric (strings .ToLower (gvk .Kind )),
76
- TerminationDuration : terminationDurationMetric (strings .ToLower (gvk .Kind )),
80
+ ConditionDuration : conditionDurationMetric (strings .ToLower (gvk .Kind ), lo . Map ( options . MetricLabels , func ( k string , _ int ) string { return toPrometheusLabel ( k ) }) ... ),
81
+ ConditionCount : conditionCountMetric (strings .ToLower (gvk .Kind ), lo . Map ( options . MetricLabels , func ( k string , _ int ) string { return toPrometheusLabel ( k ) }) ... ),
82
+ ConditionCurrentStatusSeconds : conditionCurrentStatusSecondsMetric (strings .ToLower (gvk .Kind ), lo . Map ( options . MetricLabels , func ( k string , _ int ) string { return toPrometheusLabel ( k ) }) ... ),
83
+ ConditionTransitionsTotal : conditionTransitionsTotalMetric (strings .ToLower (gvk .Kind ), lo . Map ( options . MetricLabels , func ( k string , _ int ) string { return toPrometheusLabel ( k ) }) ... ),
84
+ TerminationCurrentTimeSeconds : terminationCurrentTimeSecondsMetric (strings .ToLower (gvk .Kind ), lo . Map ( options . MetricLabels , func ( k string , _ int ) string { return toPrometheusLabel ( k ) }) ... ),
85
+ TerminationDuration : terminationDurationMetric (strings .ToLower (gvk .Kind ), lo . Map ( options . MetricLabels , func ( k string , _ int ) string { return toPrometheusLabel ( k ) }) ... ),
77
86
}
78
87
}
79
88
@@ -111,6 +120,18 @@ func (c *GenericObjectController[T]) Reconcile(ctx context.Context, req reconcil
111
120
return c .reconcile (ctx , req , NewUnstructuredAdapter [T ](object .New [T ]()))
112
121
}
113
122
123
+ func (c * Controller [T ]) toAdditionalMetricLabels (obj Object ) map [string ]string {
124
+ return lo .SliceToMap (c .additionalMetricLabels , func (label string ) (string , string ) { return toPrometheusLabel (label ), obj .GetLabels ()[label ] })
125
+ }
126
+
127
+ func toPrometheusLabel (k string ) string {
128
+ unsupportedChars := []string {"/" , "." }
129
+ for _ , char := range unsupportedChars {
130
+ k = strings .ReplaceAll (k , char , "_" )
131
+ }
132
+ return k
133
+ }
134
+
114
135
func (c * Controller [T ]) reconcile (ctx context.Context , req reconcile.Request , o Object ) (reconcile.Result , error ) {
115
136
if err := c .kubeClient .Get (ctx , req .NamespacedName , o ); err != nil {
116
137
if errors .IsNotFound (err ) {
@@ -127,7 +148,7 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o
127
148
MetricLabelName : req .Name ,
128
149
})
129
150
if deletionTS , ok := c .terminatingObjects .Load (req ); ok {
130
- c .observeHistogram (c .TerminationDuration , TerminationDuration , time .Since (deletionTS .(* metav1.Time ).Time ).Seconds (), map [ string ] string {} )
151
+ c .observeHistogram (c .TerminationDuration , TerminationDuration , time .Since (deletionTS .(* metav1.Time ).Time ).Seconds (), c . toAdditionalMetricLabels ( o ) )
131
152
}
132
153
if finalizers , ok := c .observedFinalizers .LoadAndDelete (req ); ok {
133
154
for _ , finalizer := range finalizers .([]string ) {
@@ -148,10 +169,10 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o
148
169
}
149
170
150
171
if o .GetDeletionTimestamp () != nil {
151
- c .setGaugeMetric (c .TerminationCurrentTimeSeconds , TerminationCurrentTimeSeconds , time .Since (o .GetDeletionTimestamp ().Time ).Seconds (), map [string ]string {
172
+ c .setGaugeMetric (c .TerminationCurrentTimeSeconds , TerminationCurrentTimeSeconds , time .Since (o .GetDeletionTimestamp ().Time ).Seconds (), lo . Assign ( map [string ]string {
152
173
MetricLabelNamespace : req .Namespace ,
153
174
MetricLabelName : req .Name ,
154
- })
175
+ }, c . toAdditionalMetricLabels ( o )) )
155
176
c .terminatingObjects .Store (req , o .GetDeletionTimestamp ())
156
177
}
157
178
@@ -164,20 +185,20 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o
164
185
c .observedConditions .Store (req , currentConditions )
165
186
166
187
for _ , condition := range o .GetConditions () {
167
- c .setGaugeMetric (c .ConditionCount , ConditionCount , 1 , map [string ]string {
188
+ c .setGaugeMetric (c .ConditionCount , ConditionCount , 1 , lo . Assign ( map [string ]string {
168
189
MetricLabelNamespace : req .Namespace ,
169
190
MetricLabelName : req .Name ,
170
191
pmetrics .LabelType : condition .Type ,
171
192
MetricLabelConditionStatus : string (condition .Status ),
172
193
pmetrics .LabelReason : condition .Reason ,
173
- })
174
- c .setGaugeMetric (c .ConditionCurrentStatusSeconds , ConditionCurrentStatusSeconds , time .Since (condition .LastTransitionTime .Time ).Seconds (), map [string ]string {
194
+ }, c . toAdditionalMetricLabels ( o )) )
195
+ c .setGaugeMetric (c .ConditionCurrentStatusSeconds , ConditionCurrentStatusSeconds , time .Since (condition .LastTransitionTime .Time ).Seconds (), lo . Assign ( map [string ]string {
175
196
MetricLabelNamespace : req .Namespace ,
176
197
MetricLabelName : req .Name ,
177
198
pmetrics .LabelType : condition .Type ,
178
199
MetricLabelConditionStatus : string (condition .Status ),
179
200
pmetrics .LabelReason : condition .Reason ,
180
- })
201
+ }, c . toAdditionalMetricLabels ( o )) )
181
202
}
182
203
183
204
for _ , observedCondition := range observedConditions .List () {
@@ -220,19 +241,19 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o
220
241
continue
221
242
}
222
243
// A condition transitions if it either didn't exist before or it has changed
223
- c .incCounterMetric (c .ConditionTransitionsTotal , ConditionTransitionsTotal , map [string ]string {
244
+ c .incCounterMetric (c .ConditionTransitionsTotal , ConditionTransitionsTotal , lo . Assign ( map [string ]string {
224
245
pmetrics .LabelType : condition .Type ,
225
246
MetricLabelConditionStatus : string (condition .Status ),
226
247
pmetrics .LabelReason : condition .Reason ,
227
- })
248
+ }, c . toAdditionalMetricLabels ( o )) )
228
249
if observedCondition == nil {
229
250
continue
230
251
}
231
252
duration := condition .LastTransitionTime .Time .Sub (observedCondition .LastTransitionTime .Time ).Seconds ()
232
- c .observeHistogram (c .ConditionDuration , ConditionDuration , duration , map [string ]string {
253
+ c .observeHistogram (c .ConditionDuration , ConditionDuration , duration , lo . Assign ( map [string ]string {
233
254
pmetrics .LabelType : observedCondition .Type ,
234
255
MetricLabelConditionStatus : string (observedCondition .Status ),
235
- })
256
+ }, c . toAdditionalMetricLabels ( o )) )
236
257
c .eventRecorder .Event (o , v1 .EventTypeNormal , condition .Type , fmt .Sprintf ("Status condition transitioned, Type: %s, Status: %s -> %s, Reason: %s%s" ,
237
258
condition .Type ,
238
259
observedCondition .Status ,
@@ -263,7 +284,7 @@ func (c *Controller[T]) setGaugeMetric(current pmetrics.GaugeMetric, deprecated
263
284
}
264
285
265
286
func (c * Controller [T ]) deleteGaugeMetric (current pmetrics.GaugeMetric , deprecated pmetrics.GaugeMetric , labels map [string ]string ) {
266
- current .Delete (labels )
287
+ current .DeletePartialMatch (labels )
267
288
if c .emitDeprecatedMetrics {
268
289
labels [pmetrics .LabelKind ] = c .gvk .Kind
269
290
labels [pmetrics .LabelGroup ] = c .gvk .Group
0 commit comments