Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metrics (3/4) #550

Merged
merged 1 commit into from
Aug 10, 2021
Merged

Metrics (3/4) #550

merged 1 commit into from
Aug 10, 2021

Conversation

kuritka
Copy link
Collaborator

@kuritka kuritka commented Jul 22, 2021

related to #124

In this pull request I implemented the metrics system in k8gb. I added one metric - counter
incrementing the number of successful reconciliation loops. Adding specific metrics is part of the next PR.
Like the logger, the metrics are accessed through a single singleton object and like the logger it is initialized.

var log = logging.Logger()

var m = metrics.Metrics()

then anywhere in the code we can use this:

// increase the number of successful reconciliations
m.ReconciliationIncrement()

The singleton is initialized in the main() function using the metrics.Init(*config) function.
In case the singleton is not initialized, it will be set to the default value and all metrics will
start with the prefix k8gb_default otherwise they will start with the prefix from the env variable K8GB_NAMESPACE.

In addition to the singleton, I have changed the original metrics and added tests at the package level.

In addition to the singleton, I significantly refactored the original metrics and added tests at the package level.

The prometheus metrics implementation uses reflection. In short, I read the collectors structure during the runtime:

type collectors struct {
	HealthyRecords *prometheus.GaugeVec
	IngressHostsPerStatus *prometheus.GaugeVec
	ZoneUpdateTotal prometheus.Counter
	ReconciliationTotal prometheus.Counter
}

Now I know all the types and names. From this I generate the map : .
I will then use the map for Register(), Unregister() and Get().

is the name of the metric and is generated from the name of the structure in collectors.
ReconciliationTotal => for example: k8gb_gslb_healthy_records (k8gb is K8GB_NAMESPACE)

Get() is only for testing purposes - and is public only because controller_tests use metrics.
I'll consider deleting tests from controller_tests and leaving only terratests and unit_tests at the package level (96% coverage now).
But that will come in the next PR.

Signed-off-by: kuritka kuritka@gmail.com

related to #124

In this pull request I implemented the metrics system in k8gb. I added one metric - counter
incrementing the number of successful reconciliation loops. Adding specific metrics is part of the next PR.
Like the logger, the metrics are accessed through a single singleton object and like the logger it is initialized.

```go
var log = logging.Logger()

var m = metrics.Metrics()
```
then anywhere in the code we can use this:
```
// increase the number of successful reconciliations
m.ReconciliationIncrement()
```
The singleton is initialized in the `main()` function using the `metrics.Init(*config)` function.
In case the singleton is not initialized, it will be set to the default value and all metrics will
start with the prefix `k8gb_default` otherwise they will start with the prefix from the env variable `K8GB_NAMESPACE`.

In addition to the singleton, I have changed the original metrics and added tests at the package level.

In addition to the singleton, I significantly refactored the original metrics and added tests at the package level.

The prometheus metrics implementation uses reflection. In short, I read the collectors structure during the runtime:
```go
type collectors struct {
	HealthyRecords *prometheus.GaugeVec
	IngressHostsPerStatus *prometheus.GaugeVec
	ZoneUpdateTotal prometheus.Counter
	ReconciliationTotal prometheus.Counter
}
```
Now I know all the types and names. From this I generate the map <name>: <instance>.
I will then use the map for Register(), Unregister() and Get().

<name> is the name of the metric and is generated from the name of the structure in `collectors`.
ReconciliationTotal => for example: `k8gb_gslb_healthy_records` (k8gb is `K8GB_NAMESPACE`)

`Get()` is only for testing purposes - and is public only because controller_tests use metrics.
I'll consider deleting tests from controller_tests and leaving only terratests and unit_tests at the package level (96% coverage now).
But that will come in the next PR.

Signed-off-by: kuritka <kuritka@gmail.com>
Copy link
Member

@ytsarev ytsarev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive stuff, but quite complex. Added some comments.


var (
o sync.Once
metrics = nop()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain this nop() thing?

Copy link
Collaborator Author

@kuritka kuritka Aug 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, thx:

first of all, the logger / metrics recorder should have a default state even if the metrics.Init(config) function is not called yet. This doesn't matter during runtime, because Init(config) is registered soon after loading configuration.

The problem is when we run a random test - for example a provider test where Init(config) is not explicitly called. Metrics will be called on nil object and an error will occur. Similarly, logging, which is used across the application, passes through tests without having to explicitly initialize it before each test. And that's because Nop() inits logger silently.

This way, the metric always has a default (empty but not nil) behavior (uses this prefix), and the behavior of the metric can only be changed once by calling metrics.Init(config).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By calling nop in the var or init section it is guaranteed that the object will be initialized right after the package is loaded, so I don't have to worry about whether the metrics is nil or not

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool, thanks for the explanation!

if err != nil {
return err
}
m.UpdateIngressHostsPerStatusMetric(gslb, gslb.Status.ServiceHealth)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be multiple services per Gslb resource. How do we handle that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure I understand the question.

We are currently using 3 metrics and I wanted to include the expansion of new metrics in the next PR.

Screenshot 2021-08-10 at 10 48 00

Screenshot 2021-08-10 at 10 49 51

Screenshot 2021-08-10 at 10 49 28

Screenshot 2021-08-10 at 10 50 56

Copy link
Member

@ytsarev ytsarev Aug 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@ytsarev ytsarev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@kuritka kuritka merged commit 40dbe6a into master Aug 10, 2021
@kuritka kuritka deleted the prometheus_no3_metrics_global_object branch August 10, 2021 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants