-
Notifications
You must be signed in to change notification settings - Fork 5.1k
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
Extract metrics to own package and refactor implementations #1968
Conversation
@aantono I think this is also interesting for you, I would be glad if you can have a look too :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you move the code to middleware/metrics/
?
package metrics | ||
|
||
import ( | ||
"net/http" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you reorganize the imports?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the notice, done.
@marco-jantke I think the name change for the label from P.S. Just looked at the code and didn't actually find the place where the change took place. Inside the original |
@aantono my fault I mixed it up. It was |
metrics/metrics.go
Outdated
// NewVoidRegistry is a noop implementation of metrics.Registry. | ||
// It is used to avoid nil checking in components that do metric collections. | ||
func NewVoidRegistry() Registry { | ||
return &voidRegistry{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove voidRegistry
struct and replace this struct by multiRegistry
struct.
return &multiRegistry{
reqDurationHistogram: &voidHistogram{},
reqsCounter: &voidCounter{},
retriesCounter: &voidCounter{},
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I am using the standardRegistry
now.
metrics/metrics.go
Outdated
} | ||
} | ||
|
||
type voidRegistry struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove voidRegistry
struct because it's the same struct (and methods) as multiRegistry
struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I am using the standardRegistry
now.
metrics/metrics_test.go
Outdated
cRetriesCounter := collectingRegistry.RetriesCounter().(*collectingCounter) | ||
|
||
if cReqsCounter.counterValue != 1 { | ||
t.Errorf("Got value %v for ReqsCounter, want %v", cReqsCounter.counterValue, 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you replace %v
by %f
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. I also extracted the 1
to a value with proper float64 type, so that I can use %f
both times.
metrics/metrics_test.go
Outdated
t.Errorf("Got value %v for ReqsCounter, want %v", cReqsCounter.counterValue, 1) | ||
} | ||
if cReqDurationHistogram.lastHistogramValue != 2 { | ||
t.Errorf("Got last observation %v for ReqDurationHistogram, want %v", cReqDurationHistogram.lastHistogramValue, 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you replace %v
by %f
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, done.
metrics/metrics_test.go
Outdated
t.Errorf("Got last observation %v for ReqDurationHistogram, want %v", cReqDurationHistogram.lastHistogramValue, 2) | ||
} | ||
if cRetriesCounter.counterValue != 3 { | ||
t.Errorf("Got value %v for RetriesCounter, want %v", cRetriesCounter.counterValue, 3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you replace %v
by %f
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, done.
metrics/metrics.go
Outdated
return r.reqsCounter | ||
} | ||
|
||
// ReqsCounter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove or add the good comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added it by accident. Removed the comment. I think they are not necessary on "Getters" and the method's struct is not exported.
metrics/prometheus_test.go
Outdated
for _, label := range family.Metric[0].Label { | ||
val, ok := test.labels[*label.Name] | ||
if !ok { | ||
t.Errorf("'%s' metric contains unexpected label '%s'", test.name, label) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you replace '%s'
by %q
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Adapted the error message.
metrics/prometheus_test.go
Outdated
if !ok { | ||
t.Errorf("'%s' metric contains unexpected label '%s'", test.name, label) | ||
} else if val != *label.Value { | ||
t.Errorf("label '%s' in metric '%s' has wrong value '%s'", label, test.name, *label.Value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you replace '%s'
by %q
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Adapted and improved the error message.
metrics/prometheus_test.go
Outdated
for _, test := range tests { | ||
family := findMetricFamily(test.name, metricsFamilies) | ||
if family == nil { | ||
t.Errorf("gathered metrics do not contain '%s'", test.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you replace '%s'
by %q
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, done.
metrics/prometheus_test.go
Outdated
cv := uint(family.Metric[0].Counter.GetValue()) | ||
expectedCv := uint(1) | ||
if cv != expectedCv { | ||
t.Errorf("gathered metrics do not contain correct value for total retries, got '%d' expected '%d'", cv, expectedCv) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you replace '%d'
by %q
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't sound right to me. When I change this it gets from:
gathered metrics do not contain correct value for total requests, got 2 expected 23
to
gathered metrics do not contain correct value for total requests, got '\x02' expected '\x17'
.
Maybe though I should rather use floats for comparison and print messages with %f
. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done.
ca62bdb
to
2db1e86
Compare
Thanks for the review @ldez. I rebased the branch to latest master version and addressed all your comments. |
@marco-jantke
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@marco-jantke Great job, it really simplifies the metrics :)
I have one comment though.
server/server.go
Outdated
@@ -53,6 +54,8 @@ type Server struct { | |||
routinesPool *safe.Pool | |||
leadership *cluster.Leadership | |||
defaultForwardingRoundTripper http.RoundTripper | |||
metricsRegistry metrics.Registry | |||
metricsEnabled bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not fan of adding this flag. Can't we remove it and use metricsRegistry
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
metricsRegistry
now always contains an implementation. When nothing is configured it will hold a VoidRegistry
. This means we could write a util function like metricsEnabled()
and it will return the ok
of a type assertion on the metrics implementation. You would prefer this way?
Personally I am not sure about what is the more explicit option, but both is not hard to implement so you are free to choose :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I double checked the implementation and my described approach above won't work. All different registries that we create through e.g. NewVoidRegistry
, RegisterStatsd
, RegisterPrometheus
.. will have the internal type standardRegistry
and therefore I can't use a type assertion.
This means a metricsEnabled()
utility would have to check on the global configuration once more. E.g. if globalConfiguration.Web.Metrics.Prometheus != nil || globalConfiguration.Web.Metrics.Statsd != nil ...
. Personally I think that having this conditions only in registerMetricClients
is the more clean approach and tend to leave it like it is. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed on Slack refactored now towards an IsEnabled()
method on the Registry
.
886ea96
to
60d4dfb
Compare
metrics/metrics.go
Outdated
} | ||
|
||
type standardRegistry struct { | ||
isEnabled bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you rename isEnabled
to enabled
?
But keep the method name IsEnabled()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry enabled
not enable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing, done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome @marco-jantke !
LGTM
in order to avoid a name clash for the soon to come metrics package.
This commit is just refactoring and should provide the same functionality as before. It extracts the metrics implementation into its own package and makes metrics therefore extendable to be used outside of the middleware package in the future. Another design goal is to simplify and align the initialisation of the Metrics implementations. Each Metric provider should only be initialised once and reused when configuration reloads happen. This commit accomplishes this.
in order to have less state on the server struct.
ed480cb
to
6a4221b
Compare
This PR should be the first step to an extension of the metrics implementations. In our Traefik fork that we are running in my project we have extended the Prometheus implementation considerably, but to get there some refactoring and alignment has to be done first. I created once already a PR for the extension of the Prometheus implementation, but it was diverging after Datadog and StatsD implementations were added at the same time to Traefik.
Therefore this PR is only about refactoring of the structure and extracting the Metrics implementations into their own package. Once the structure is aligned I will create follow-up metrics extending the functionality. I think it is a better approach this way, as it should make reviewing considerably easier.
Motivation
This PR is changing the design and I want to elaborate a bit in what it changes it and why:
metrics
package: This is important in order to be able to track metrics outside of themiddleware
package. Meaningful metrics could be for example configuration reloads. Those happen in theserver
package and so an implementation inside ofmiddleware
can not be used (cyclic dependency). Another positive point about the extraction is the better separation of concerns in regards and a more clear project structure.NewServer
when Traefik boots up, which is the desired behaviour. Through the alignment of the initialisation the logic is now more straightforward and easier to understand.