Skip to content

Commit

Permalink
Merge pull request #1 from matlockx/master
Browse files Browse the repository at this point in the history
adds statsd reporter
  • Loading branch information
oliverbestmann authored Jun 14, 2019
2 parents 49d07c2 + 737e6b1 commit ff500a4
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 1 deletion.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
module github.com/eSailors/go-datadog
module github.com/flachnetz/go-datadog

require (
github.com/DataDog/datadog-go v2.2.0+incompatible
github.com/go-check/check v0.0.0-20180628173108-788fd7840127
github.com/kr/pretty v0.1.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165
github.com/stretchr/testify v1.3.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4=
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk=
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
175 changes: 175 additions & 0 deletions metrics_statsd_reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package datadog

import (
"fmt"
"log"
"runtime"
"strconv"
"time"

"github.com/DataDog/datadog-go/statsd"
metrics "github.com/rcrowley/go-metrics"
)

// ReporterOption is function-option used during the construction of a *Reporter
type ReporterOption func(*StatsDReporter) error

// Reporter represents a metrics registry, and the statsd client the metrics
// will be flushed to
type StatsDReporter struct {
// Registry matrices that need to be reported to the Client
Registry metrics.Registry

// Client is the configured statsd instance
Client *statsd.Client

// Time interval between two consecutive Flush calls to store the matrix
// value to the Client.
interval time.Duration

// Reporter type configuration settings
tags []string
ss map[string]int64

// Optional parameters
percentiles []float64
p []string
}

// NewReporter creates a new Reporter with a pre-configured statsd client.
func NewReporter(r metrics.Registry, client *statsd.Client, d time.Duration, options ...ReporterOption) (*StatsDReporter, error) {
if r == nil {
r = metrics.DefaultRegistry
}

percentiles := []float64{0.75, 0.95, 0.99, 0.999}
percentileNames, err := getPercentileNames(percentiles)
if err != nil {
return nil, err
}
reporter := &StatsDReporter{
Client: client,
Registry: r,
percentiles: percentiles,
p: percentileNames,
interval: d,
ss: make(map[string]int64),
}
for _, option := range options {
if err := option(reporter); err != nil {
return nil, err
}
}
return reporter, nil
}

func getPercentileNames(percentiles []float64) ([]string, error) {
names := make([]string, len(percentiles))
for i, percentile := range percentiles {
if percentile <= 0 || percentile >= 1 {
return nil, fmt.Errorf("Percentile must lie in interval (0,1)")
}
names[i] = ".percentile." + strconv.FormatFloat(percentile, 'f', -1, 64)[2:]
}
return names, nil
}

// Flush is a blocking exporter function which reports metrics in the registry
// to the statsd client, flushing every d duration
func (r *StatsDReporter) Flush() {
defer func() {
if rec := recover(); rec != nil {
handlePanic(rec)
}
}()

for range time.Tick(r.interval) {
if err := r.FlushOnce(); err != nil {
log.Println(err)
}
}
}

// FlushOnce submits a snapshot submission of the registry to DataDog. This can
// be used in a loop similarly to FlushWithInterval for custom error handling or
// data submission variations.
func (r *StatsDReporter) FlushOnce() error {
r.Registry.Each(func(name string, i interface{}) {
switch metric := i.(type) {
case metrics.Counter:
v := metric.Count()
l := r.ss[name]
r.Client.Count(name+".count", v-l, r.tags, 1)
r.ss[name] = v

case metrics.Gauge:
r.Client.Gauge(name+".value", float64(metric.Value()), r.tags, 1)

case metrics.GaugeFloat64:
r.Client.Gauge(name+".value", metric.Value(), r.tags, 1)

case metrics.Histogram:
ms := metric.Snapshot()

r.Client.Gauge(name+".count", float64(ms.Count()), r.tags, 1)
r.Client.Gauge(name+".max", float64(ms.Max()), r.tags, 1)
r.Client.Gauge(name+".min", float64(ms.Min()), r.tags, 1)
r.Client.Gauge(name+".mean", ms.Mean(), r.tags, 1)
r.Client.Gauge(name+".stddev", ms.StdDev(), r.tags, 1)
r.Client.Gauge(name+".median", time.Duration(ms.Percentile(0.5)).Seconds()*1000, r.tags, 1)

if len(r.percentiles) > 0 {
values := ms.Percentiles(r.percentiles)
for i, p := range r.p {
r.Client.Gauge(name+p, values[i], r.tags, 1)
}
}

case metrics.Meter:
ms := metric.Snapshot()

r.Client.Gauge(name+".count", float64(ms.Count()), r.tags, 1)
r.Client.Gauge(name+".rate.1min", ms.Rate1(), r.tags, 1)
r.Client.Gauge(name+".rate.5min", ms.Rate5(), r.tags, 1)
r.Client.Gauge(name+".rate.15min", ms.Rate15(), r.tags, 1)
r.Client.Gauge(name+".rate.mean", ms.RateMean(), r.tags, 1)

case metrics.Timer:
ms := metric.Snapshot()

r.Client.Gauge(name+".count", float64(ms.Count()), r.tags, 1)
r.Client.Gauge(name+".max", time.Duration(ms.Max()).Seconds()*1000, r.tags, 1)
r.Client.Gauge(name+".min", time.Duration(ms.Min()).Seconds()*1000, r.tags, 1)
r.Client.Gauge(name+".mean", time.Duration(ms.Mean()).Seconds()*1000, r.tags, 1)
r.Client.Gauge(name+".stddev", time.Duration(ms.StdDev()).Seconds()*1000, r.tags, 1)

r.Client.Gauge(name+".median", time.Duration(ms.Percentile(0.5)).Seconds()*1000, r.tags, 1)

r.Client.Gauge(name+".rate.1min", ms.Rate1(), r.tags, 1)
r.Client.Gauge(name+".rate.5min", ms.Rate5(), r.tags, 1)
r.Client.Gauge(name+".rate.15min", ms.Rate15(), r.tags, 1)
r.Client.Gauge(name+".rate.mean", ms.RateMean(), r.tags, 1)

if len(r.percentiles) > 0 {
values := ms.Percentiles(r.percentiles)
for i, p := range r.p {
r.Client.Gauge(name+p, time.Duration(values[i]).Seconds()*1000, r.tags, 1)
}
}
}
})

return nil
}

func handlePanic(rec interface{}) {
callers := ""
for i := 2; true; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
callers = callers + fmt.Sprintf("%v:%v\n", file, line)
}
log.Printf("Recovered from panic: %#v \n%v", rec, callers)
}

0 comments on commit ff500a4

Please sign in to comment.