Skip to content

Commit c4e5e0e

Browse files
authored
Metrics (#111)
* adding metrics * add comments * refactor metrics * cleanup comments
1 parent b354727 commit c4e5e0e

File tree

8 files changed

+187
-41
lines changed

8 files changed

+187
-41
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/onsi/ginkgo/v2 v2.17.2
1010
github.com/onsi/gomega v1.33.1
1111
github.com/pkg/errors v0.9.1
12+
github.com/prometheus/client_golang v1.18.0
1213
github.com/sap/go-generics v0.2.9
1314
github.com/spf13/pflag v1.0.5
1415
golang.org/x/time v0.5.0
@@ -63,7 +64,6 @@ require (
6364
github.com/modern-go/reflect2 v1.0.2 // indirect
6465
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
6566
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
66-
github.com/prometheus/client_golang v1.18.0 // indirect
6767
github.com/prometheus/client_model v0.6.0 // indirect
6868
github.com/prometheus/common v0.47.0 // indirect
6969
github.com/prometheus/procfs v0.12.0 // indirect

go.sum

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
105105
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
106106
github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
107107
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
108-
github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
109-
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
110108
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
111109
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
112110
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -124,8 +122,6 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
124122
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
125123
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
126124
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
127-
github.com/sap/go-generics v0.2.8 h1:IwRzXSf2g4g6hB5aABbaxg+EMntgx9nj9HjW+VpxYzo=
128-
github.com/sap/go-generics v0.2.8/go.mod h1:vTfK+QKsUkJrFtdRybKGKFzFslMNNUbEof0hovZXJ2U=
129125
github.com/sap/go-generics v0.2.9 h1:EleSr+FFw1vctoNO3ye6P6H0BwBGrmhGErE6s7/Ntvo=
130126
github.com/sap/go-generics v0.2.9/go.mod h1:eVi3GoySdkMTd7uvwehG7ZOE8tJGHtSlZt4bUEj3liI=
131127
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
@@ -263,8 +259,6 @@ k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCf
263259
k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
264260
sigs.k8s.io/cli-utils v0.35.0 h1:dfSJaF1W0frW74PtjwiyoB4cwdRygbHnC7qe7HF0g/Y=
265261
sigs.k8s.io/cli-utils v0.35.0/go.mod h1:ITitykCJxP1vaj1Cew/FZEaVJ2YsTN9Q71m02jebkoE=
266-
sigs.k8s.io/controller-runtime v0.18.0 h1:Z7jKuX784TQSUL1TIyeuF7j8KXZ4RtSX0YgtjKcSTME=
267-
sigs.k8s.io/controller-runtime v0.18.0/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw=
268262
sigs.k8s.io/controller-runtime v0.18.1 h1:RpWbigmuiylbxOCLy0tGnq1cU1qWPwNIQzoJk+QeJx4=
269263
sigs.k8s.io/controller-runtime v0.18.1/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw=
270264
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=

internal/cluster/factory.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"crypto/sha256"
1010
"encoding/json"
1111
"fmt"
12+
"net/http"
1213
"reflect"
1314
"sync"
1415
"time"
@@ -25,20 +26,22 @@ import (
2526
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
2627
"sigs.k8s.io/controller-runtime/pkg/client"
2728

29+
"github.com/sap/component-operator-runtime/internal/metrics"
2830
"github.com/sap/component-operator-runtime/pkg/types"
2931
)
3032

3133
type ClientFactory struct {
32-
mutex sync.Mutex
33-
name string
34-
config *rest.Config
35-
scheme *runtime.Scheme
36-
clients map[string]*clientImpl
34+
mutex sync.Mutex
35+
name string
36+
controllerName string
37+
config *rest.Config
38+
scheme *runtime.Scheme
39+
clients map[string]*clientImpl
3740
}
3841

3942
const validity = 15 * time.Minute
4043

41-
func NewClientFactory(name string, config *rest.Config, schemeBuilders []types.SchemeBuilder) (*ClientFactory, error) {
44+
func NewClientFactory(name string, controllerName string, config *rest.Config, schemeBuilders []types.SchemeBuilder) (*ClientFactory, error) {
4245
scheme := runtime.NewScheme()
4346
if err := clientgoscheme.AddToScheme(scheme); err != nil {
4447
return nil, err
@@ -56,10 +59,11 @@ func NewClientFactory(name string, config *rest.Config, schemeBuilders []types.S
5659
}
5760

5861
factory := &ClientFactory{
59-
name: name,
60-
config: config,
61-
scheme: scheme,
62-
clients: make(map[string]*clientImpl),
62+
name: name,
63+
controllerName: controllerName,
64+
config: config,
65+
scheme: scheme,
66+
clients: make(map[string]*clientImpl),
6367
}
6468

6569
go func() {
@@ -72,10 +76,10 @@ func NewClientFactory(name string, config *rest.Config, schemeBuilders []types.S
7276
if clnt.validUntil.Before(now) {
7377
clnt.eventBroadcaster.Shutdown()
7478
// TODO: add some (debug) log output when client is removed; unfortunately, we have no logger in here ...
75-
// TODO: add metrics about running clients
7679
delete(factory.clients, key)
7780
}
7881
}
82+
metrics.ActiveClients.WithLabelValues(factory.controllerName).Set(float64(len(factory.clients)))
7983
factory.mutex.Unlock()
8084
}
8185
}()
@@ -122,6 +126,12 @@ func (f *ClientFactory) Get(kubeConfig []byte, impersonationUser string, imperso
122126
return clnt, nil
123127
}
124128

129+
config.Wrap(func(rt http.RoundTripper) http.RoundTripper {
130+
return roundTripperFunc(func(r *http.Request) (*http.Response, error) {
131+
metrics.Requests.WithLabelValues(f.controllerName, r.Method).Inc()
132+
return rt.RoundTrip(r)
133+
})
134+
})
125135
httpClient, err := rest.HTTPClientFor(config)
126136
if err != nil {
127137
return nil, err
@@ -145,10 +155,11 @@ func (f *ClientFactory) Get(kubeConfig []byte, impersonationUser string, imperso
145155
validUntil: time.Now().Add(validity),
146156
}
147157
f.clients[key] = clnt
158+
metrics.CreatedClients.WithLabelValues(f.controllerName).Inc()
159+
metrics.ActiveClients.WithLabelValues(f.controllerName).Set(float64(len(f.clients)))
148160

149161
// TODO: add some (debug) log output when new client is created; unfortunately, we have no logger in here ...
150162
// maybe we could (at least in Get()) get one from the reconcile context ...
151-
// TODO: add metrics about running clients
152163
return clnt, nil
153164
}
154165

@@ -160,3 +171,11 @@ func sha256sum(data any) string {
160171
sha256sum := sha256.Sum256(dataAsJson)
161172
return string(sha256sum[:])
162173
}
174+
175+
type roundTripperFunc func(r *http.Request) (*http.Response, error)
176+
177+
var _ http.RoundTripper = roundTripperFunc(nil)
178+
179+
func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
180+
return f(r)
181+
}

internal/metrics/metrics.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and component-operator-runtime contributors
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package metrics
7+
8+
import (
9+
"github.com/prometheus/client_golang/prometheus"
10+
11+
"sigs.k8s.io/controller-runtime/pkg/metrics"
12+
)
13+
14+
const (
15+
prefix = "component_operator_runtime"
16+
)
17+
18+
var (
19+
Reconciles = prometheus.NewCounterVec(
20+
prometheus.CounterOpts{
21+
Name: prefix + "_reconcile_total",
22+
Help: "Total number of reconciliations per controller",
23+
},
24+
[]string{"controller"},
25+
)
26+
ReconcileErrors = prometheus.NewCounterVec(
27+
prometheus.CounterOpts{
28+
Name: prefix + "_reconcile_errors_total",
29+
Help: "Total number of reconciliation errors per controller and type",
30+
},
31+
[]string{"controller", "type"},
32+
)
33+
Requests = prometheus.NewCounterVec(
34+
prometheus.CounterOpts{
35+
Name: prefix + "_requests_total",
36+
Help: "Kubernetes API server requests per controller and method",
37+
},
38+
[]string{"controller", "method"},
39+
)
40+
Operations = prometheus.NewCounterVec(
41+
prometheus.CounterOpts{
42+
Name: prefix + "_operations_total",
43+
Help: "Dependent operations per controller and action",
44+
},
45+
[]string{"controller", "action"},
46+
)
47+
CreatedClients = prometheus.NewCounterVec(
48+
prometheus.CounterOpts{
49+
Name: prefix + "_created_clients_total",
50+
Help: "Kubernetes API clients created since the controller was started",
51+
},
52+
[]string{"controller"},
53+
)
54+
ActiveClients = prometheus.NewGaugeVec(
55+
prometheus.GaugeOpts{
56+
Name: prefix + "_active_clients_total",
57+
Help: "Currently active Kubernetes API clients",
58+
},
59+
[]string{"controller"},
60+
)
61+
)
62+
63+
func init() {
64+
metrics.Registry.MustRegister(
65+
Reconciles,
66+
ReconcileErrors,
67+
Requests,
68+
Operations,
69+
CreatedClients,
70+
ActiveClients,
71+
)
72+
}

pkg/component/context.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,19 @@ import (
1212
"github.com/sap/component-operator-runtime/pkg/cluster"
1313
)
1414

15-
type reconcilerNameContextKey struct{}
16-
type clientContextKey struct{}
17-
type componentContextKey struct{}
18-
type componentDigestContextKey struct{}
15+
type (
16+
reconcilerNameContextKeyType struct{}
17+
clientContextKeyType struct{}
18+
componentContextKeyType struct{}
19+
componentDigestContextKeyType struct{}
20+
)
21+
22+
var (
23+
reconcilerNameContextKey = reconcilerNameContextKeyType{}
24+
clientContextKey = clientContextKeyType{}
25+
componentContextKey = componentContextKeyType{}
26+
componentDigestContextKey = componentDigestContextKeyType{}
27+
)
1928

2029
func newContext(ctx context.Context) *reconcileContext {
2130
return &reconcileContext{Context: ctx}
@@ -26,44 +35,44 @@ type reconcileContext struct {
2635
}
2736

2837
func (c *reconcileContext) WithReconcilerName(reconcilerName string) *reconcileContext {
29-
return &reconcileContext{Context: context.WithValue(c, reconcilerNameContextKey{}, reconcilerName)}
38+
return &reconcileContext{Context: context.WithValue(c, reconcilerNameContextKey, reconcilerName)}
3039
}
3140

3241
func (c *reconcileContext) WithClient(clnt cluster.Client) *reconcileContext {
33-
return &reconcileContext{Context: context.WithValue(c, clientContextKey{}, clnt)}
42+
return &reconcileContext{Context: context.WithValue(c, clientContextKey, clnt)}
3443
}
3544

3645
func (c *reconcileContext) WithComponent(component Component) *reconcileContext {
37-
return &reconcileContext{Context: context.WithValue(c, componentContextKey{}, component)}
46+
return &reconcileContext{Context: context.WithValue(c, componentContextKey, component)}
3847
}
3948

4049
func (c *reconcileContext) WithComponentDigest(componentDigest string) *reconcileContext {
41-
return &reconcileContext{Context: context.WithValue(c, componentDigestContextKey{}, componentDigest)}
50+
return &reconcileContext{Context: context.WithValue(c, componentDigestContextKey, componentDigest)}
4251
}
4352

4453
func ReconcilerNameFromContext(ctx context.Context) (string, error) {
45-
if reconcilerName, ok := ctx.Value(reconcilerNameContextKey{}).(string); ok {
54+
if reconcilerName, ok := ctx.Value(reconcilerNameContextKey).(string); ok {
4655
return reconcilerName, nil
4756
}
4857
return "", fmt.Errorf("reconciler name not found in context")
4958
}
5059

5160
func ClientFromContext(ctx context.Context) (cluster.Client, error) {
52-
if clnt, ok := ctx.Value(clientContextKey{}).(cluster.Client); ok {
61+
if clnt, ok := ctx.Value(clientContextKey).(cluster.Client); ok {
5362
return clnt, nil
5463
}
5564
return nil, fmt.Errorf("client not found in context")
5665
}
5766

5867
func ComponentFromContext(ctx context.Context) (Component, error) {
59-
if component, ok := ctx.Value(componentContextKey{}).(Component); ok {
68+
if component, ok := ctx.Value(componentContextKey).(Component); ok {
6069
return component, nil
6170
}
6271
return nil, fmt.Errorf("component not found in context")
6372
}
6473

6574
func ComponentDigestFromContext(ctx context.Context) (string, error) {
66-
if componentDigest, ok := ctx.Value(componentDigestContextKey{}).(string); ok {
75+
if componentDigest, ok := ctx.Value(componentDigestContextKey).(string); ok {
6776
return componentDigest, nil
6877
}
6978
return "", fmt.Errorf("component digest not found in context")

0 commit comments

Comments
 (0)