Skip to content

Commit b4a8ffa

Browse files
Add support for oci cli metrics (#268)
* Add support for OCI Client metrics
1 parent 47fd880 commit b4a8ffa

File tree

5 files changed

+192
-74
lines changed

5 files changed

+192
-74
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
Copyright (c) 2023 Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metrics
18+
19+
import (
20+
"net/http"
21+
"strings"
22+
"time"
23+
24+
"github.com/oracle/oci-go-sdk/v65/common"
25+
)
26+
27+
// HttpRequestDispatcherWrapper is a wrapper around standard common.HTTPRequestDispatcher to handle
28+
// metrics
29+
type HttpRequestDispatcherWrapper struct {
30+
dispatcher common.HTTPRequestDispatcher
31+
region string
32+
}
33+
34+
// Do is wrapper implementation of common.HTTPRequestDispatcher Do method
35+
func (wrapper HttpRequestDispatcherWrapper) Do(req *http.Request) (*http.Response, error) {
36+
t := time.Now()
37+
resp, err := wrapper.dispatcher.Do(req)
38+
defer func() {
39+
// taken from https://docs.oracle.com/en-us/iaas/Content/API/Concepts/usingapi.htm
40+
// a URL consists of a version string and then a resource
41+
urlSplit := strings.Split(req.URL.Path, "/")
42+
if len(urlSplit) < 2 {
43+
return
44+
}
45+
resource := urlSplit[2]
46+
IncRequestCounter(err, resource, req.Method, wrapper.region, resp)
47+
ObserverRequestDuration(resource, req.Method, wrapper.region, time.Since(t))
48+
}()
49+
return resp, err
50+
}
51+
52+
// NewHttpRequestDispatcherWrapper creates a new instance of HttpRequestDispatcherWrapper
53+
func NewHttpRequestDispatcherWrapper(dispatcher common.HTTPRequestDispatcher, region string) HttpRequestDispatcherWrapper {
54+
return HttpRequestDispatcherWrapper{
55+
dispatcher: dispatcher,
56+
region: region,
57+
}
58+
}

cloud/metrics/metrics.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
Copyright (c) 2023 Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metrics
18+
19+
import (
20+
"net/http"
21+
"strconv"
22+
"time"
23+
24+
"github.com/prometheus/client_golang/prometheus"
25+
"sigs.k8s.io/controller-runtime/pkg/metrics"
26+
)
27+
28+
type verb string
29+
30+
const (
31+
SubSystemOCI = "oci"
32+
OCIRequestsTotal = "requests_total"
33+
Duration = "request_duration"
34+
Resource = "resource"
35+
StatusCode = "status_code"
36+
Operation = "operation"
37+
38+
Region = "region"
39+
Get string = "get"
40+
List string = "list"
41+
Create string = "create"
42+
Update string = "update"
43+
Delete string = "delete"
44+
)
45+
46+
var (
47+
ociRequestCounter = prometheus.NewCounterVec(
48+
prometheus.CounterOpts{
49+
Subsystem: SubSystemOCI,
50+
Name: OCIRequestsTotal,
51+
Help: "OCI API requests total.",
52+
},
53+
[]string{Resource, StatusCode, Operation, Region},
54+
)
55+
ociRequestDurationSeconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
56+
Subsystem: SubSystemOCI,
57+
Name: Duration,
58+
Help: "Duration/Latency of HTTP requests to OCI",
59+
}, []string{Resource, Operation, Region})
60+
)
61+
62+
// IncRequestCounter increments the request count metric for the given resource.
63+
// Unknown errors from request dispatcher will have response code of 999
64+
func IncRequestCounter(err error, resource string, operation string, region string, response *http.Response) {
65+
statusCode := 999
66+
if err == nil {
67+
statusCode = response.StatusCode
68+
}
69+
ociRequestCounter.With(prometheus.Labels{
70+
Resource: resource,
71+
Operation: operation,
72+
StatusCode: strconv.Itoa(statusCode),
73+
Region: region,
74+
}).Inc()
75+
}
76+
77+
// ObserverRequestDuration observes the request duration for the partcular OCI request
78+
func ObserverRequestDuration(resource string, operation string, region string, duration time.Duration) {
79+
ociRequestDurationSeconds.With(prometheus.Labels{
80+
Resource: resource,
81+
Operation: operation,
82+
Region: region,
83+
}).Observe(duration.Seconds())
84+
}
85+
86+
func init() {
87+
metrics.Registry.MustRegister(ociRequestCounter)
88+
metrics.Registry.MustRegister(ociRequestDurationSeconds)
89+
}

cloud/scope/clients.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/go-logr/logr"
2626
"github.com/oracle/cluster-api-provider-oci/api/v1beta2"
27+
"github.com/oracle/cluster-api-provider-oci/cloud/metrics"
2728
"github.com/oracle/cluster-api-provider-oci/cloud/services/base"
2829
"github.com/oracle/cluster-api-provider-oci/cloud/services/compute"
2930
"github.com/oracle/cluster-api-provider-oci/cloud/services/computemanagement"
@@ -133,7 +134,7 @@ func (c *ClientProvider) GetRegion() (string, error) {
133134
}
134135

135136
func (c *ClientProvider) createClients(region string) (OCIClients, error) {
136-
vcnClient, err := c.createVncClient(region, c.ociAuthConfigProvider, c.Logger)
137+
vcnClient, err := c.createVcnClient(region, c.ociAuthConfigProvider, c.Logger)
137138
if err != nil {
138139
return OCIClients{}, err
139140
}
@@ -182,16 +183,18 @@ func (c *ClientProvider) createClients(region string) (OCIClients, error) {
182183
}, err
183184
}
184185

185-
func (c *ClientProvider) createVncClient(region string, ociAuthConfigProvider common.ConfigurationProvider, logger *logr.Logger) (*core.VirtualNetworkClient, error) {
186+
func (c *ClientProvider) createVcnClient(region string, ociAuthConfigProvider common.ConfigurationProvider, logger *logr.Logger) (*core.VirtualNetworkClient, error) {
186187
vcnClient, err := core.NewVirtualNetworkClientWithConfigurationProvider(ociAuthConfigProvider)
187188
if err != nil {
188189
logger.Error(err, "unable to create OCI VCN Client")
189190
return nil, err
190191
}
191192
vcnClient.SetRegion(region)
193+
dispatcher := vcnClient.HTTPClient
194+
vcnClient.HTTPClient = metrics.NewHttpRequestDispatcherWrapper(dispatcher, region)
192195

193196
if c.certOverride != nil {
194-
if client, ok := vcnClient.HTTPClient.(*http.Client); ok {
197+
if client, ok := dispatcher.(*http.Client); ok {
195198
err = c.setCerts(client)
196199
if err != nil {
197200
logger.Error(err, "unable to create OCI VCN Client")
@@ -218,9 +221,11 @@ func (c *ClientProvider) createNLbClient(region string, ociAuthConfigProvider co
218221
return nil, err
219222
}
220223
nlbClient.SetRegion(region)
224+
dispatcher := nlbClient.HTTPClient
225+
nlbClient.HTTPClient = metrics.NewHttpRequestDispatcherWrapper(dispatcher, region)
221226

222227
if c.certOverride != nil {
223-
if client, ok := nlbClient.HTTPClient.(*http.Client); ok {
228+
if client, ok := dispatcher.(*http.Client); ok {
224229
err = c.setCerts(client)
225230
if err != nil {
226231
logger.Error(err, "unable to create OCI NetworkLoadBalancer Client")
@@ -246,9 +251,11 @@ func (c *ClientProvider) createLBClient(region string, ociAuthConfigProvider com
246251
return nil, err
247252
}
248253
lbClient.SetRegion(region)
254+
dispatcher := lbClient.HTTPClient
255+
lbClient.HTTPClient = metrics.NewHttpRequestDispatcherWrapper(dispatcher, region)
249256

250257
if c.certOverride != nil {
251-
if client, ok := lbClient.HTTPClient.(*http.Client); ok {
258+
if client, ok := dispatcher.(*http.Client); ok {
252259
err = c.setCerts(client)
253260
if err != nil {
254261
logger.Error(err, "unable to create OCI Loadbalancer Client")
@@ -274,9 +281,11 @@ func (c *ClientProvider) createIdentityClient(region string, ociAuthConfigProvid
274281
return nil, err
275282
}
276283
identityClt.SetRegion(region)
284+
dispatcher := identityClt.HTTPClient
285+
identityClt.HTTPClient = metrics.NewHttpRequestDispatcherWrapper(dispatcher, region)
277286

278287
if c.certOverride != nil {
279-
if client, ok := identityClt.HTTPClient.(*http.Client); ok {
288+
if client, ok := dispatcher.(*http.Client); ok {
280289
err = c.setCerts(client)
281290
if err != nil {
282291
logger.Error(err, "unable to create OCI Identity Client")
@@ -295,16 +304,18 @@ func (c *ClientProvider) createIdentityClient(region string, ociAuthConfigProvid
295304
return &identityClt, nil
296305
}
297306

298-
func (c *ClientProvider) createComputeClient(region string, ociAuthConfigProvider common.ConfigurationProvider, logger *logr.Logger) (*core.ComputeClient, error) {
307+
func (c *ClientProvider) createComputeClient(region string, ociAuthConfigProvider common.ConfigurationProvider, logger *logr.Logger) (compute.ComputeClient, error) {
299308
computeClient, err := core.NewComputeClientWithConfigurationProvider(ociAuthConfigProvider)
300309
if err != nil {
301310
logger.Error(err, "unable to create OCI Compute Client")
302311
return nil, err
303312
}
304313
computeClient.SetRegion(region)
314+
dispatcher := computeClient.HTTPClient
315+
computeClient.HTTPClient = metrics.NewHttpRequestDispatcherWrapper(dispatcher, region)
305316

306317
if c.certOverride != nil {
307-
if client, ok := computeClient.HTTPClient.(*http.Client); ok {
318+
if client, ok := dispatcher.(*http.Client); ok {
308319
err = c.setCerts(client)
309320
if err != nil {
310321
logger.Error(err, "unable to create OCI Compute Client")
@@ -330,9 +341,11 @@ func (c *ClientProvider) createComputeManagementClient(region string, ociAuthCon
330341
return nil, err
331342
}
332343
computeManagementClient.SetRegion(region)
344+
dispatcher := computeManagementClient.HTTPClient
345+
computeManagementClient.HTTPClient = metrics.NewHttpRequestDispatcherWrapper(dispatcher, region)
333346

334347
if c.certOverride != nil {
335-
if client, ok := computeManagementClient.HTTPClient.(*http.Client); ok {
348+
if client, ok := dispatcher.(*http.Client); ok {
336349
err = c.setCerts(client)
337350
if err != nil {
338351
logger.Error(err, "unable to create OCI Compute Management Client")
@@ -358,9 +371,11 @@ func (c *ClientProvider) createContainerEngineClient(region string, ociAuthConfi
358371
return nil, err
359372
}
360373
containerEngineClt.SetRegion(region)
374+
dispatcher := containerEngineClt.HTTPClient
375+
containerEngineClt.HTTPClient = metrics.NewHttpRequestDispatcherWrapper(dispatcher, region)
361376

362377
if c.certOverride != nil {
363-
if client, ok := containerEngineClt.HTTPClient.(*http.Client); ok {
378+
if client, ok := dispatcher.(*http.Client); ok {
364379
err = c.setCerts(client)
365380
if err != nil {
366381
logger.Error(err, "unable to create OCI Container Engine Client")

go.mod

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/onsi/gomega v1.27.5
1111
github.com/oracle/oci-go-sdk/v65 v65.29.0
1212
github.com/pkg/errors v0.9.1
13+
github.com/prometheus/client_golang v1.15.1
1314
github.com/spf13/pflag v1.0.5
1415
gopkg.in/yaml.v2 v2.4.0
1516
k8s.io/api v0.26.1
@@ -36,7 +37,7 @@ require (
3637
github.com/beorn7/perks v1.0.1 // indirect
3738
github.com/blang/semver v3.5.1+incompatible // indirect
3839
github.com/blang/semver/v4 v4.0.0 // indirect
39-
github.com/cespare/xxhash/v2 v2.1.2 // indirect
40+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
4041
github.com/coredns/caddy v1.1.0 // indirect
4142
github.com/coredns/corefile-migration v1.0.20 // indirect
4243
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -76,7 +77,7 @@ require (
7677
github.com/magiconair/properties v1.8.7 // indirect
7778
github.com/mailru/easyjson v0.7.7 // indirect
7879
github.com/mattn/go-isatty v0.0.17 // indirect
79-
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
80+
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
8081
github.com/mitchellh/copystructure v1.2.0 // indirect
8182
github.com/mitchellh/mapstructure v1.5.0 // indirect
8283
github.com/mitchellh/reflectwalk v1.0.2 // indirect
@@ -87,10 +88,10 @@ require (
8788
github.com/opencontainers/image-spec v1.0.2 // indirect
8889
github.com/pelletier/go-toml v1.9.5 // indirect
8990
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
90-
github.com/prometheus/client_golang v1.14.0 // indirect
91+
github.com/prometheus/client_golang v1.15.1 // indirect
9192
github.com/prometheus/client_model v0.3.0 // indirect
92-
github.com/prometheus/common v0.37.0 // indirect
93-
github.com/prometheus/procfs v0.8.0 // indirect
93+
github.com/prometheus/common v0.42.0 // indirect
94+
github.com/prometheus/procfs v0.9.0 // indirect
9495
github.com/shopspring/decimal v1.3.1 // indirect
9596
github.com/sirupsen/logrus v1.8.1 // indirect
9697
github.com/sony/gobreaker v0.5.0 // indirect
@@ -116,7 +117,7 @@ require (
116117
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
117118
google.golang.org/appengine v1.6.7 // indirect
118119
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
119-
google.golang.org/protobuf v1.28.1 // indirect
120+
google.golang.org/protobuf v1.30.0 // indirect
120121
gopkg.in/inf.v0 v0.9.1 // indirect
121122
gopkg.in/ini.v1 v1.67.0 // indirect
122123
gopkg.in/yaml.v3 v3.0.1 // indirect

0 commit comments

Comments
 (0)