From 34ef24e14be560df477b43b51f15866c655d97ca Mon Sep 17 00:00:00 2001 From: Andrew Wilkins Date: Wed, 28 Mar 2018 17:36:58 +0800 Subject: [PATCH] module/apmgometrics: go-metrics gatherer --- module/apmgometrics/gatherer.go | 40 +++++++++++++++++++++ module/apmgometrics/gatherer_test.go | 54 ++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 module/apmgometrics/gatherer.go create mode 100644 module/apmgometrics/gatherer_test.go diff --git a/module/apmgometrics/gatherer.go b/module/apmgometrics/gatherer.go new file mode 100644 index 000000000..257233936 --- /dev/null +++ b/module/apmgometrics/gatherer.go @@ -0,0 +1,40 @@ +package apmgometrics + +import ( + "context" + + "github.com/rcrowley/go-metrics" + + "github.com/elastic/apm-agent-go" +) + +// Wrap wraps r, a go-metrics Registry, so that it can be used +// as an elasticapm.MetricsGatherer. +func Wrap(r metrics.Registry) elasticapm.MetricsGatherer { + return gatherer{r} +} + +type gatherer struct { + r metrics.Registry +} + +// GatherMEtrics gathers metrics into m. +func (g gatherer) GatherMetrics(ctx context.Context, m *elasticapm.Metrics) error { + g.r.Each(func(name string, v interface{}) { + switch v := v.(type) { + case metrics.Counter: + // NOTE(axw) in go-metrics, counters can go up and down, + // hence we use a gauge here. Should we provide config + // to allow a user to specify that a counter is always + // increasing, hence represent it as a counter type? + m.AddGauge(name, nil, float64(v.Count())) + case metrics.Gauge: + m.AddGauge(name, nil, float64(v.Value())) + case metrics.GaugeFloat64: + m.AddGauge(name, nil, v.Value()) + default: + // TODO(axw) Meter, Timer, Histogram, EWMA + } + }) + return nil +} diff --git a/module/apmgometrics/gatherer_test.go b/module/apmgometrics/gatherer_test.go new file mode 100644 index 000000000..9c002bfc1 --- /dev/null +++ b/module/apmgometrics/gatherer_test.go @@ -0,0 +1,54 @@ +package apmgometrics_test + +import ( + "testing" + + "github.com/rcrowley/go-metrics" + "github.com/stretchr/testify/assert" + + "github.com/elastic/apm-agent-go" + "github.com/elastic/apm-agent-go/model" + "github.com/elastic/apm-agent-go/module/apmgometrics" + "github.com/elastic/apm-agent-go/transport/transporttest" +) + +func TestGatherer(t *testing.T) { + r := metrics.NewRegistry() + httpReqsTotal := metrics.GetOrRegisterCounter("http.requests_total", r) + httpReqsInflight := metrics.GetOrRegisterGauge("http.requests_inflight", r) + httpReqsTotal.Inc(123) + httpReqsInflight.Update(10) + + g := apmgometrics.Wrap(r) + metrics := gatherMetrics(g) + + assert.Equal(t, []*model.Metrics{{ + Samples: map[string]model.Metric{ + "http.requests_total": { + // go-metrics's counters are gauges + // by our definition, hence the Value + // field is set and Count is not. + Value: newFloat64(123), + }, + "http.requests_inflight": { + Value: newFloat64(10), + }, + }, + }}, metrics) +} + +func gatherMetrics(g elasticapm.MetricsGatherer) []*model.Metrics { + tracer, transport := transporttest.NewRecorderTracer() + defer tracer.Close() + tracer.AddMetricsGatherer(g) + tracer.SendMetrics(nil) + metrics := transport.Payloads()[0].Metrics() + for _, m := range metrics { + m.Timestamp = model.Time{} + } + return metrics +} + +func newFloat64(v float64) *float64 { + return &v +}