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
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
dd166e5
rename global var metrics to stats
mjeri 0406958
extract metrics implementation to own package
mjeri ea4fef4
fix import order
mjeri 047d853
implement review feedback
mjeri 310ac8d
fix formatting
mjeri baba69a
implement review feedback
mjeri 9a385f4
remove obosolete and wrong comment
mjeri 9ee02d6
extract metricsEnabled to own Registry method
mjeri 6a4221b
rename field isEnabled to enabled
mjeri File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package metrics | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/containous/traefik/log" | ||
"github.com/containous/traefik/safe" | ||
"github.com/containous/traefik/types" | ||
kitlog "github.com/go-kit/kit/log" | ||
"github.com/go-kit/kit/metrics/dogstatsd" | ||
) | ||
|
||
var datadogClient = dogstatsd.New("traefik.", kitlog.LoggerFunc(func(keyvals ...interface{}) error { | ||
log.Info(keyvals) | ||
return nil | ||
})) | ||
|
||
var datadogTicker *time.Ticker | ||
|
||
// Metric names consistent with https://github.com/DataDog/integrations-extras/pull/64 | ||
const ( | ||
ddMetricsReqsName = "requests.total" | ||
ddMetricsLatencyName = "request.duration" | ||
ddRetriesTotalName = "backend.retries.total" | ||
) | ||
|
||
// RegisterDatadog registers the metrics pusher if this didn't happen yet and creates a datadog Registry instance. | ||
func RegisterDatadog(config *types.Datadog) Registry { | ||
if datadogTicker == nil { | ||
datadogTicker = initDatadogClient(config) | ||
} | ||
|
||
registry := &standardRegistry{ | ||
enabled: true, | ||
reqsCounter: datadogClient.NewCounter(ddMetricsReqsName, 1.0), | ||
reqDurationHistogram: datadogClient.NewHistogram(ddMetricsLatencyName, 1.0), | ||
retriesCounter: datadogClient.NewCounter(ddRetriesTotalName, 1.0), | ||
} | ||
|
||
return registry | ||
} | ||
|
||
func initDatadogClient(config *types.Datadog) *time.Ticker { | ||
address := config.Address | ||
if len(address) == 0 { | ||
address = "localhost:8125" | ||
} | ||
pushInterval, err := time.ParseDuration(config.PushInterval) | ||
if err != nil { | ||
log.Warnf("Unable to parse %s into pushInterval, using 10s as default value", config.PushInterval) | ||
pushInterval = 10 * time.Second | ||
} | ||
|
||
report := time.NewTicker(pushInterval) | ||
|
||
safe.Go(func() { | ||
datadogClient.SendLoop(report.C, "udp", address) | ||
}) | ||
|
||
return report | ||
} | ||
|
||
// StopDatadog stops internal datadogTicker which controls the pushing of metrics to DD Agent and resets it to `nil`. | ||
func StopDatadog() { | ||
if datadogTicker != nil { | ||
datadogTicker.Stop() | ||
} | ||
datadogTicker = nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package metrics | ||
|
||
import ( | ||
"net/http" | ||
"strconv" | ||
"testing" | ||
"time" | ||
|
||
"github.com/containous/traefik/types" | ||
"github.com/stvp/go-udp-testing" | ||
) | ||
|
||
func TestDatadog(t *testing.T) { | ||
udp.SetAddr(":18125") | ||
// This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond | ||
udp.Timeout = 5 * time.Second | ||
|
||
datadogRegistry := RegisterDatadog(&types.Datadog{Address: ":18125", PushInterval: "1s"}) | ||
defer StopDatadog() | ||
|
||
if !datadogRegistry.IsEnabled() { | ||
t.Errorf("DatadogRegistry should return true for IsEnabled()") | ||
} | ||
|
||
expected := []string{ | ||
// We are only validating counts, as it is nearly impossible to validate latency, since it varies every run | ||
"traefik.requests.total:1.000000|c|#service:test,code:404,method:GET\n", | ||
"traefik.requests.total:1.000000|c|#service:test,code:200,method:GET\n", | ||
"traefik.backend.retries.total:2.000000|c|#service:test\n", | ||
"traefik.request.duration:10000.000000|h|#service:test,code:200", | ||
} | ||
|
||
udp.ShouldReceiveAll(t, expected, func() { | ||
datadogRegistry.ReqsCounter().With("service", "test", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1) | ||
datadogRegistry.ReqsCounter().With("service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1) | ||
datadogRegistry.ReqDurationHistogram().With("service", "test", "code", strconv.Itoa(http.StatusOK)).Observe(10000) | ||
datadogRegistry.RetriesCounter().With("service", "test").Add(1) | ||
datadogRegistry.RetriesCounter().With("service", "test").Add(1) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package metrics | ||
|
||
import ( | ||
"github.com/go-kit/kit/metrics" | ||
"github.com/go-kit/kit/metrics/multi" | ||
) | ||
|
||
// Registry has to implemented by any system that wants to monitor and expose metrics. | ||
type Registry interface { | ||
// IsEnabled shows whether metrics instrumentation is enabled. | ||
IsEnabled() bool | ||
ReqsCounter() metrics.Counter | ||
ReqDurationHistogram() metrics.Histogram | ||
RetriesCounter() metrics.Counter | ||
} | ||
|
||
// NewMultiRegistry creates a new standardRegistry that wraps multiple Registries. | ||
func NewMultiRegistry(registries []Registry) Registry { | ||
reqsCounters := []metrics.Counter{} | ||
reqDurationHistograms := []metrics.Histogram{} | ||
retriesCounters := []metrics.Counter{} | ||
|
||
for _, r := range registries { | ||
reqsCounters = append(reqsCounters, r.ReqsCounter()) | ||
reqDurationHistograms = append(reqDurationHistograms, r.ReqDurationHistogram()) | ||
retriesCounters = append(retriesCounters, r.RetriesCounter()) | ||
} | ||
|
||
return &standardRegistry{ | ||
enabled: true, | ||
reqsCounter: multi.NewCounter(reqsCounters...), | ||
reqDurationHistogram: multi.NewHistogram(reqDurationHistograms...), | ||
retriesCounter: multi.NewCounter(retriesCounters...), | ||
} | ||
} | ||
|
||
type standardRegistry struct { | ||
enabled bool | ||
reqsCounter metrics.Counter | ||
reqDurationHistogram metrics.Histogram | ||
retriesCounter metrics.Counter | ||
} | ||
|
||
func (r *standardRegistry) IsEnabled() bool { | ||
return r.enabled | ||
} | ||
|
||
func (r *standardRegistry) ReqsCounter() metrics.Counter { | ||
return r.reqsCounter | ||
} | ||
|
||
func (r *standardRegistry) ReqDurationHistogram() metrics.Histogram { | ||
return r.reqDurationHistogram | ||
} | ||
|
||
func (r *standardRegistry) RetriesCounter() metrics.Counter { | ||
return r.retriesCounter | ||
} | ||
|
||
// 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 &standardRegistry{ | ||
enabled: false, | ||
reqsCounter: &voidCounter{}, | ||
reqDurationHistogram: &voidHistogram{}, | ||
retriesCounter: &voidCounter{}, | ||
} | ||
} | ||
|
||
type voidCounter struct{} | ||
|
||
func (v *voidCounter) With(labelValues ...string) metrics.Counter { return v } | ||
func (v *voidCounter) Add(delta float64) {} | ||
|
||
type voidHistogram struct{} | ||
|
||
func (h *voidHistogram) With(labelValues ...string) metrics.Histogram { return h } | ||
func (h *voidHistogram) Observe(value float64) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package metrics | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/go-kit/kit/metrics" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewVoidRegistry(t *testing.T) { | ||
registry := NewVoidRegistry() | ||
|
||
if registry.IsEnabled() { | ||
t.Errorf("VoidRegistry should not return true for IsEnabled()") | ||
} | ||
registry.ReqsCounter().With("some", "value").Add(1) | ||
registry.ReqDurationHistogram().With("some", "value").Observe(1) | ||
registry.RetriesCounter().With("some", "value").Add(1) | ||
} | ||
|
||
func TestNewMultiRegistry(t *testing.T) { | ||
registries := []Registry{newCollectingRetryMetrics(), newCollectingRetryMetrics()} | ||
registry := NewMultiRegistry(registries) | ||
|
||
registry.ReqsCounter().With("key", "requests").Add(1) | ||
registry.ReqDurationHistogram().With("key", "durations").Observe(2) | ||
registry.RetriesCounter().With("key", "retries").Add(3) | ||
|
||
for _, collectingRegistry := range registries { | ||
cReqsCounter := collectingRegistry.ReqsCounter().(*counterMock) | ||
cReqDurationHistogram := collectingRegistry.ReqDurationHistogram().(*histogramMock) | ||
cRetriesCounter := collectingRegistry.RetriesCounter().(*counterMock) | ||
|
||
wantCounterValue := float64(1) | ||
if cReqsCounter.counterValue != wantCounterValue { | ||
t.Errorf("Got value %f for ReqsCounter, want %f", cReqsCounter.counterValue, wantCounterValue) | ||
} | ||
wantHistogramValue := float64(2) | ||
if cReqDurationHistogram.lastHistogramValue != wantHistogramValue { | ||
t.Errorf("Got last observation %f for ReqDurationHistogram, want %f", cReqDurationHistogram.lastHistogramValue, wantHistogramValue) | ||
} | ||
wantCounterValue = float64(3) | ||
if cRetriesCounter.counterValue != wantCounterValue { | ||
t.Errorf("Got value %f for RetriesCounter, want %f", cRetriesCounter.counterValue, wantCounterValue) | ||
} | ||
|
||
assert.Equal(t, []string{"key", "requests"}, cReqsCounter.lastLabelValues) | ||
assert.Equal(t, []string{"key", "durations"}, cReqDurationHistogram.lastLabelValues) | ||
assert.Equal(t, []string{"key", "retries"}, cRetriesCounter.lastLabelValues) | ||
} | ||
} | ||
|
||
func newCollectingRetryMetrics() Registry { | ||
return &standardRegistry{ | ||
reqsCounter: &counterMock{}, | ||
reqDurationHistogram: &histogramMock{}, | ||
retriesCounter: &counterMock{}, | ||
} | ||
} | ||
|
||
type counterMock struct { | ||
counterValue float64 | ||
lastLabelValues []string | ||
} | ||
|
||
func (c *counterMock) With(labelValues ...string) metrics.Counter { | ||
c.lastLabelValues = labelValues | ||
return c | ||
} | ||
|
||
func (c *counterMock) Add(delta float64) { | ||
c.counterValue += delta | ||
} | ||
|
||
type histogramMock struct { | ||
lastHistogramValue float64 | ||
lastLabelValues []string | ||
} | ||
|
||
func (c *histogramMock) With(labelValues ...string) metrics.Histogram { | ||
c.lastLabelValues = labelValues | ||
return c | ||
} | ||
|
||
func (c *histogramMock) Observe(value float64) { | ||
c.lastHistogramValue = value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package metrics | ||
|
||
import ( | ||
"github.com/containous/traefik/types" | ||
"github.com/go-kit/kit/metrics/prometheus" | ||
stdprometheus "github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
const ( | ||
metricNamePrefix = "traefik_" | ||
|
||
reqsTotalName = metricNamePrefix + "requests_total" | ||
reqDurationName = metricNamePrefix + "request_duration_seconds" | ||
retriesTotalName = metricNamePrefix + "backend_retries_total" | ||
) | ||
|
||
// RegisterPrometheus registers all Prometheus metrics. | ||
// It must be called only once and failing to register the metrics will lead to a panic. | ||
func RegisterPrometheus(config *types.Prometheus) Registry { | ||
buckets := []float64{0.1, 0.3, 1.2, 5.0} | ||
if config.Buckets != nil { | ||
buckets = config.Buckets | ||
} | ||
|
||
reqCounter := prometheus.NewCounterFrom(stdprometheus.CounterOpts{ | ||
Name: reqsTotalName, | ||
Help: "How many HTTP requests processed, partitioned by status code and method.", | ||
}, []string{"service", "code", "method"}) | ||
reqDurationHistogram := prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ | ||
Name: reqDurationName, | ||
Help: "How long it took to process the request.", | ||
Buckets: buckets, | ||
}, []string{"service", "code"}) | ||
retryCounter := prometheus.NewCounterFrom(stdprometheus.CounterOpts{ | ||
Name: retriesTotalName, | ||
Help: "How many request retries happened in total.", | ||
}, []string{"service"}) | ||
|
||
return &standardRegistry{ | ||
enabled: true, | ||
reqsCounter: reqCounter, | ||
reqDurationHistogram: reqDurationHistogram, | ||
retriesCounter: retryCounter, | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.