qmp-testing-suite/golang-producer-consumer/vendor/gitlab-app.eng.qops.net/golang/metrics/reporter.go

210 lines
5.3 KiB
Go
Executable File

package metrics
import (
"fmt"
"os"
"time"
"github.com/alexcesaro/statsd"
"github.com/pkg/errors"
)
type config struct {
prefix string
addr string
errorHandler func(error)
}
// Option is a type for configuring a StatsDReporter
type Option func(*config) error
// Address sets the address where we send metrics
// The default is "telegraf:8125". This is so "telegraf"
// can be mounted into docker containers as a network interface
func Address(addr string) Option {
return Option(func(c *config) error {
c.addr = addr
return nil
})
}
// AddressFromEnv reads the address from the specified env variable.
// If the specified env variable is not set it will return an error.
func AddressFromEnv(varName string) Option {
value, isSet := os.LookupEnv(varName)
if !isSet {
return Option(func(c *config) error {
return fmt.Errorf("missing variable (%s) for metrics.AddressFromEnv", varName)
})
}
return Option(func(c *config) error {
c.addr = value
return nil
})
}
// ErrorHandler sets the function called when an error
// happens when sending metrics to the remote metrics store
func ErrorHandler(h func(error)) Option {
return Option(func(c *config) error {
c.errorHandler = h
return nil
})
}
// StatsDReporter is a concrete implementation of Reporter that sends metrics to a statsd service.
// It will buffer and periodically flush metrics.
type StatsDReporter struct {
client client
errorHandler func(error)
}
// MetricTimer is a concrete implementation of Timer that records a timing.
type MetricTimer struct {
Reporter *StatsDReporter
Start time.Time
Name string
Tags []Meta
}
// Record internally calls RecordTiming to submit the MetricsTimer.
func (t *MetricTimer) Record() {
t.Reporter.RecordTiming(t.Name, time.Since(t.Start), t.Tags...)
}
// EnableStatsDReporter will create a new StatsDReporter as the DefaultReporter or return an error.
func EnableStatsDReporter(prefix string, opts ...Option) error {
r, err := NewReporter(prefix, opts...)
if err != nil {
return err
}
DefaultReporter = r
return nil
}
// NewReporter creates a new StatsDReporter.
// If a connection cannot be estabilished with the remote metrics service
// a DiscardReporter and and error will be returned.
func NewReporter(prefix string, opts ...Option) (Reporter, error) {
cfg := &config{
prefix: prefix,
addr: "telegraf:8125",
errorHandler: func(error) {},
}
for _, o := range opts {
if err := o(cfg); err != nil {
return DiscardReporter, err
}
}
client, err := statsd.New(
statsd.Prefix(cfg.prefix),
statsd.Address(cfg.addr),
statsd.TagsFormat(statsd.InfluxDB),
statsd.ErrorHandler(cfg.errorHandler),
)
if err != nil {
return DiscardReporter, errors.Wrap(err, "error creating client/connection for StatsDReporter")
}
reporter := &StatsDReporter{
client: client,
errorHandler: cfg.errorHandler,
}
return reporter, nil
}
func mapMetasToSliceMetas(tags []Meta) []string {
var kvs []string
for _, t := range tags {
kvs = append(kvs, t.Key, t.Value)
}
return kvs
}
func (mr *StatsDReporter) getClientWithMetas(tags ...Meta) client {
if tagsInSlice := mapMetasToSliceMetas(tags); len(tagsInSlice) > 0 {
return mr.client.Clone(
statsd.Tags(tagsInSlice...),
)
}
return mr.client
}
// IncCounter increments a metric count.
func (mr *StatsDReporter) IncCounter(name string, tags ...Meta) {
if name == "" {
mr.errorHandler(errors.New("missing name for IncCounter func"))
return
}
mr.getClientWithMetas(tags...).Increment(name)
}
// AddToCounter adds a given value to a metric count.
func (mr *StatsDReporter) AddToCounter(name string, value int64, tags ...Meta) {
if name == "" {
mr.errorHandler(errors.New("missing name for AddToCounter func"))
return
}
mr.getClientWithMetas(tags...).Count(name, value)
}
// UpdateGauge sets a new value for a metric.
func (mr *StatsDReporter) UpdateGauge(name string, value int64, tags ...Meta) {
if name == "" {
mr.errorHandler(errors.New("missing name for UpdateGauge func"))
return
}
mr.getClientWithMetas(tags...).Gauge(name, value)
}
// RecordHistogram submits a new timing metric.
func (mr *StatsDReporter) RecordHistogram(name string, v float64, tags ...Meta) {
if name == "" {
mr.errorHandler(errors.New("missing name for RecordHistogram func"))
return
}
mr.getClientWithMetas(tags...).Histogram(name, v)
}
// RecordTiming submits a new timing metric.
func (mr *StatsDReporter) RecordTiming(name string, d time.Duration, tags ...Meta) {
if name == "" {
mr.errorHandler(errors.New("missing name for RecordTiming func"))
return
}
mr.getClientWithMetas(tags...).Timing(name, d.Nanoseconds()/1000000)
}
// NewTimer creates a MetricsTimer that can be recorded later.
// A common pattern is to create and immediately defer the closure of a Timer:
//
// defer reporter.NewTimer("http_request", Tag("protocol", "http2")).Record()
func (mr *StatsDReporter) NewTimer(name string, tags ...Meta) Timer {
return &MetricTimer{
Reporter: mr,
Start: time.Now(),
Name: name,
Tags: tags,
}
}
// Flush flushes all metrics that are currently buffered.
func (mr *StatsDReporter) Flush() {
mr.client.Flush()
}
// Close will close the StatsDReporter and all open statsd connections.
func (mr *StatsDReporter) Close() error {
mr.client.Close()
return nil
}
var _ Reporter = &StatsDReporter{}