Skip to content

Commit a2d2f27

Browse files
authored
Add object specific Metric (#98)
1 parent a84e1d1 commit a2d2f27

File tree

5 files changed

+808
-400
lines changed

5 files changed

+808
-400
lines changed

status/controller.go

Lines changed: 107 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import (
99
"time"
1010

1111
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/runtime"
13+
"k8s.io/apimachinery/pkg/runtime/schema"
1214

1315
pmetrics "github.com/awslabs/operatorpkg/metrics"
1416
"github.com/awslabs/operatorpkg/object"
17+
"github.com/awslabs/operatorpkg/option"
1518
"github.com/samber/lo"
1619
v1 "k8s.io/api/core/v1"
1720
"k8s.io/apimachinery/pkg/api/errors"
@@ -24,24 +27,60 @@ import (
2427
)
2528

2629
type Controller[T Object] struct {
27-
kubeClient client.Client
28-
eventRecorder record.EventRecorder
29-
observedConditions sync.Map // map[reconcile.Request]ConditionSet
30-
terminatingObjects sync.Map // map[reconcile.Request]DeletionTimestamp
30+
gvk schema.GroupVersionKind
31+
kubeClient client.Client
32+
eventRecorder record.EventRecorder
33+
observedConditions sync.Map // map[reconcile.Request]ConditionSet
34+
terminatingObjects sync.Map // map[reconcile.Request]DeletionTimestamp
35+
emitDeprecatedMetrics bool
36+
ConditionDuration pmetrics.ObservationMetric
37+
ConditionCount pmetrics.GaugeMetric
38+
ConditionCurrentStatusSeconds pmetrics.GaugeMetric
39+
ConditionTransitionsTotal pmetrics.CounterMetric
40+
TerminationCurrentTimeSeconds pmetrics.GaugeMetric
41+
TerminationDuration pmetrics.ObservationMetric
3142
}
3243

33-
func NewController[T Object](client client.Client, eventRecorder record.EventRecorder) *Controller[T] {
44+
type Option struct {
45+
// Current list of deprecated metrics
46+
// - operator_status_condition_transitions_total
47+
// - operator_status_condition_transition_seconds
48+
// - operator_status_condition_current_status_seconds
49+
// - operator_status_condition_count
50+
// - operator_termination_current_time_seconds
51+
// - operator_termination_duration_seconds
52+
EmitDeprecatedMetrics bool
53+
}
54+
55+
func EmitDeprecatedMetrics(o *Option) {
56+
o.EmitDeprecatedMetrics = true
57+
}
58+
59+
func NewController[T Object](client client.Client, eventRecorder record.EventRecorder, opts ...option.Function[Option]) *Controller[T] {
60+
options := option.Resolve(opts...)
61+
obj := reflect.New(reflect.TypeOf(*new(T)).Elem()).Interface().(runtime.Object)
62+
obj.GetObjectKind().SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind())
63+
gvk := object.GVK(obj)
64+
3465
return &Controller[T]{
35-
kubeClient: client,
36-
eventRecorder: eventRecorder,
66+
gvk: gvk,
67+
kubeClient: client,
68+
eventRecorder: eventRecorder,
69+
emitDeprecatedMetrics: options.EmitDeprecatedMetrics,
70+
ConditionDuration: conditionDurationMetric(strings.ToLower(gvk.Kind)),
71+
ConditionCount: conditionCountMetric(strings.ToLower(gvk.Kind)),
72+
ConditionCurrentStatusSeconds: conditionCurrentStatusSecondsMetric(strings.ToLower(gvk.Kind)),
73+
ConditionTransitionsTotal: conditionTransitionsTotalMetric(strings.ToLower(gvk.Kind)),
74+
TerminationCurrentTimeSeconds: terminationCurrentTimeSecondsMetric(strings.ToLower(gvk.Kind)),
75+
TerminationDuration: terminationDurationMetric(strings.ToLower(gvk.Kind)),
3776
}
3877
}
3978

4079
func (c *Controller[T]) Register(_ context.Context, m manager.Manager) error {
4180
return controllerruntime.NewControllerManagedBy(m).
4281
For(object.New[T]()).
4382
WithOptions(controller.Options{MaxConcurrentReconciles: 10}).
44-
Named(fmt.Sprintf("operatorpkg.%s.status", strings.ToLower(reflect.TypeOf(object.New[T]()).Elem().Name()))).
83+
Named(fmt.Sprintf("operatorpkg.%s.status", strings.ToLower(c.gvk.Kind))).
4584
Complete(c)
4685
}
4786

@@ -50,12 +89,12 @@ func (c *Controller[T]) Reconcile(ctx context.Context, req reconcile.Request) (r
5089
}
5190

5291
type GenericObjectController[T client.Object] struct {
53-
*Controller[*unstructuredAdapter]
92+
*Controller[*unstructuredAdapter[T]]
5493
}
5594

56-
func NewGenericObjectController[T client.Object](client client.Client, eventRecorder record.EventRecorder) *GenericObjectController[T] {
95+
func NewGenericObjectController[T client.Object](client client.Client, eventRecorder record.EventRecorder, opts ...option.Function[Option]) *GenericObjectController[T] {
5796
return &GenericObjectController[T]{
58-
Controller: NewController[*unstructuredAdapter](client, eventRecorder),
97+
Controller: NewController[*unstructuredAdapter[T]](client, eventRecorder, opts...),
5998
}
6099
}
61100

@@ -68,37 +107,26 @@ func (c *GenericObjectController[T]) Register(_ context.Context, m manager.Manag
68107
}
69108

70109
func (c *GenericObjectController[T]) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
71-
return c.reconcile(ctx, req, NewUnstructuredAdapter(object.New[T]()))
110+
return c.reconcile(ctx, req, NewUnstructuredAdapter[T](object.New[T]()))
72111
}
73112

74113
func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o Object) (reconcile.Result, error) {
75-
gvk := object.GVK(o)
76-
77114
if err := c.kubeClient.Get(ctx, req.NamespacedName, o); err != nil {
78115
if errors.IsNotFound(err) {
79-
ConditionCount.DeletePartialMatch(map[string]string{
80-
pmetrics.LabelGroup: gvk.Group,
81-
pmetrics.LabelKind: gvk.Kind,
116+
c.deletePartialMatchGaugeMetric(c.ConditionCount, ConditionCount, map[string]string{
82117
MetricLabelNamespace: req.Namespace,
83118
MetricLabelName: req.Name,
84119
})
85-
ConditionCurrentStatusSeconds.DeletePartialMatch(map[string]string{
86-
pmetrics.LabelGroup: gvk.Group,
87-
pmetrics.LabelKind: gvk.Kind,
120+
c.deletePartialMatchGaugeMetric(c.ConditionCurrentStatusSeconds, ConditionCurrentStatusSeconds, map[string]string{
88121
MetricLabelNamespace: req.Namespace,
89122
MetricLabelName: req.Name,
90123
})
91-
TerminationCurrentTimeSeconds.DeletePartialMatch(map[string]string{
124+
c.deletePartialMatchGaugeMetric(c.TerminationCurrentTimeSeconds, TerminationCurrentTimeSeconds, map[string]string{
92125
MetricLabelNamespace: req.Namespace,
93126
MetricLabelName: req.Name,
94-
pmetrics.LabelGroup: gvk.Group,
95-
pmetrics.LabelKind: gvk.Kind,
96127
})
97128
if deletionTS, ok := c.terminatingObjects.Load(req); ok {
98-
TerminationDuration.Observe(time.Since(deletionTS.(*metav1.Time).Time).Seconds(), map[string]string{
99-
pmetrics.LabelGroup: gvk.Group,
100-
pmetrics.LabelKind: gvk.Kind,
101-
})
129+
c.observeHistogram(c.TerminationDuration, TerminationDuration, time.Since(deletionTS.(*metav1.Time).Time).Seconds(), map[string]string{})
102130
}
103131
return reconcile.Result{}, nil
104132
}
@@ -114,18 +142,14 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o
114142

115143
// Detect and record condition counts
116144
for _, condition := range o.GetConditions() {
117-
ConditionCount.Set(1, map[string]string{
118-
pmetrics.LabelGroup: gvk.Group,
119-
pmetrics.LabelKind: gvk.Kind,
145+
c.setGaugeMetric(c.ConditionCount, ConditionCount, 1, map[string]string{
120146
MetricLabelNamespace: req.Namespace,
121147
MetricLabelName: req.Name,
122148
pmetrics.LabelType: condition.Type,
123149
MetricLabelConditionStatus: string(condition.Status),
124150
pmetrics.LabelReason: condition.Reason,
125151
})
126-
ConditionCurrentStatusSeconds.Set(time.Since(condition.LastTransitionTime.Time).Seconds(), map[string]string{
127-
pmetrics.LabelGroup: gvk.Group,
128-
pmetrics.LabelKind: gvk.Kind,
152+
c.setGaugeMetric(c.ConditionCurrentStatusSeconds, ConditionCurrentStatusSeconds, time.Since(condition.LastTransitionTime.Time).Seconds(), map[string]string{
129153
MetricLabelNamespace: req.Namespace,
130154
MetricLabelName: req.Name,
131155
pmetrics.LabelType: condition.Type,
@@ -134,28 +158,22 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o
134158
})
135159
}
136160
if o.GetDeletionTimestamp() != nil {
137-
TerminationCurrentTimeSeconds.Set(time.Since(o.GetDeletionTimestamp().Time).Seconds(), map[string]string{
161+
c.setGaugeMetric(c.TerminationCurrentTimeSeconds, TerminationCurrentTimeSeconds, time.Since(o.GetDeletionTimestamp().Time).Seconds(), map[string]string{
138162
MetricLabelNamespace: req.Namespace,
139163
MetricLabelName: req.Name,
140-
pmetrics.LabelGroup: gvk.Group,
141-
pmetrics.LabelKind: gvk.Kind,
142164
})
143165
c.terminatingObjects.Store(req, o.GetDeletionTimestamp())
144166
}
145167
for _, observedCondition := range observedConditions.List() {
146168
if currentCondition := currentConditions.Get(observedCondition.Type); currentCondition == nil || currentCondition.Status != observedCondition.Status {
147-
ConditionCount.Delete(map[string]string{
148-
pmetrics.LabelGroup: gvk.Group,
149-
pmetrics.LabelKind: gvk.Kind,
169+
c.deleteGaugeMetric(c.ConditionCount, ConditionCount, map[string]string{
150170
MetricLabelNamespace: req.Namespace,
151171
MetricLabelName: req.Name,
152172
pmetrics.LabelType: observedCondition.Type,
153173
MetricLabelConditionStatus: string(observedCondition.Status),
154174
pmetrics.LabelReason: observedCondition.Reason,
155175
})
156-
ConditionCurrentStatusSeconds.Delete(map[string]string{
157-
pmetrics.LabelGroup: gvk.Group,
158-
pmetrics.LabelKind: gvk.Kind,
176+
c.deleteGaugeMetric(c.ConditionCurrentStatusSeconds, ConditionCurrentStatusSeconds, map[string]string{
159177
MetricLabelNamespace: req.Namespace,
160178
MetricLabelName: req.Name,
161179
pmetrics.LabelType: observedCondition.Type,
@@ -186,9 +204,7 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o
186204
continue
187205
}
188206
// A condition transitions if it either didn't exist before or it has changed
189-
ConditionTransitionsTotal.Inc(map[string]string{
190-
pmetrics.LabelGroup: gvk.Group,
191-
pmetrics.LabelKind: gvk.Kind,
207+
c.incCounterMetric(c.ConditionTransitionsTotal, ConditionTransitionsTotal, map[string]string{
192208
pmetrics.LabelType: condition.Type,
193209
MetricLabelConditionStatus: string(condition.Status),
194210
pmetrics.LabelReason: condition.Reason,
@@ -197,9 +213,7 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o
197213
continue
198214
}
199215
duration := condition.LastTransitionTime.Time.Sub(observedCondition.LastTransitionTime.Time).Seconds()
200-
ConditionDuration.Observe(duration, map[string]string{
201-
pmetrics.LabelGroup: gvk.Group,
202-
pmetrics.LabelKind: gvk.Kind,
216+
c.observeHistogram(c.ConditionDuration, ConditionDuration, duration, map[string]string{
203217
pmetrics.LabelType: observedCondition.Type,
204218
MetricLabelConditionStatus: string(observedCondition.Status),
205219
})
@@ -213,3 +227,48 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o
213227
}
214228
return reconcile.Result{RequeueAfter: time.Second * 10}, nil
215229
}
230+
231+
func (c *Controller[T]) incCounterMetric(current pmetrics.CounterMetric, deprecated pmetrics.CounterMetric, labels map[string]string) {
232+
current.Inc(labels)
233+
if c.emitDeprecatedMetrics {
234+
labels[pmetrics.LabelKind] = c.gvk.Kind
235+
labels[pmetrics.LabelGroup] = c.gvk.Group
236+
deprecated.Inc(labels)
237+
}
238+
}
239+
240+
func (c *Controller[T]) setGaugeMetric(current pmetrics.GaugeMetric, deprecated pmetrics.GaugeMetric, value float64, labels map[string]string) {
241+
current.Set(value, labels)
242+
if c.emitDeprecatedMetrics {
243+
labels[pmetrics.LabelKind] = c.gvk.Kind
244+
labels[pmetrics.LabelGroup] = c.gvk.Group
245+
deprecated.Set(value, labels)
246+
}
247+
}
248+
249+
func (c *Controller[T]) deleteGaugeMetric(current pmetrics.GaugeMetric, deprecated pmetrics.GaugeMetric, labels map[string]string) {
250+
current.Delete(labels)
251+
if c.emitDeprecatedMetrics {
252+
labels[pmetrics.LabelKind] = c.gvk.Kind
253+
labels[pmetrics.LabelGroup] = c.gvk.Group
254+
deprecated.Delete(labels)
255+
}
256+
}
257+
258+
func (c *Controller[T]) deletePartialMatchGaugeMetric(current pmetrics.GaugeMetric, deprecated pmetrics.GaugeMetric, labels map[string]string) {
259+
current.DeletePartialMatch(labels)
260+
if c.emitDeprecatedMetrics {
261+
labels[pmetrics.LabelKind] = c.gvk.Kind
262+
labels[pmetrics.LabelGroup] = c.gvk.Group
263+
deprecated.DeletePartialMatch(labels)
264+
}
265+
}
266+
267+
func (c *Controller[T]) observeHistogram(current pmetrics.ObservationMetric, deprecated pmetrics.ObservationMetric, value float64, labels map[string]string) {
268+
current.Observe(value, labels)
269+
if c.emitDeprecatedMetrics {
270+
labels[pmetrics.LabelKind] = c.gvk.Kind
271+
labels[pmetrics.LabelGroup] = c.gvk.Group
272+
deprecated.Observe(value, labels)
273+
}
274+
}

0 commit comments

Comments
 (0)