Codebase list golang-github-go-kit-kit / 3a00cf8 metrics / cloudwatch / cloudwatch.go
3a00cf8

Tree @3a00cf8 (Download .tar.gz)

cloudwatch.go @3a00cf8raw · history · blame

package cloudwatch

import (
	"sync"

	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/metrics"
	"github.com/go-kit/kit/metrics/generic"
)

// CloudWatch ...
type CloudWatch struct {
	mtx      sync.RWMutex
	prefix   string
	svc      *cloudwatch.CloudWatch
	counters map[string]*Counter
	gauges   map[string]*Gauge
	logger   log.Logger
}

// New ...
func New(prefix string, logger log.Logger, svc *cloudwatch.CloudWatch) *CloudWatch {
	return &CloudWatch{
		prefix:   prefix,
		svc:      svc,
		counters: map[string]*Counter{},
		gauges:   map[string]*Gauge{},
		logger:   logger,
	}
}

func (cw *CloudWatch) NewCounter(name string) *Counter {
	c := NewCounter(name)
	cw.mtx.Lock()
	cw.counters[cw.prefix+name] = c
	cw.mtx.Unlock()
	return c
}

func (cw *CloudWatch) NewGauge(name string) *Gauge {
	c := NewGauge(name)
	cw.mtx.Lock()
	cw.counters[cw.prefix+name] = c
	cw.mtx.Unlock()
	return c
}

// WriteLoop is a helper method that invokes WriteTo to the passed writer every
// time the passed channel fires. This method blocks until the channel is
// closed, so clients probably want to run it in its own goroutine. For typical
// usage, create a time.Ticker and pass its C channel to this method.
func (cw *CloudWatch) WriteLoop(c <-chan time.Time) {
	for range c {
		cw.mtx.RLock()
		defer cw.mtx.RUnlock()
		now := time.Now()

		for name, c := range cw.counters {
			_, err := cw.svc.PutMetricData(&cloudwatch.PutMetricDataInput{
				Namespace: aws.String(cw.prefix),
				MetricData: []*cloudwatch.MetricDatum{
					{
						MetricName: aws.String(name),
						Dimensions: makeDimensions(c.c.LabelValues()...),
						Value:      aws.Float64(c.c.Value()),
						Timestamp:  aws.Time(now),
					},
				},
			})
			if err != nil {
				cw.logger.Log("during", "WriteLoop", "err", err)
			}
		}

		for name, g := range cw.gauges {
			_, err := cw.svc.PutMetricData(&cloudwatch.PutMetricDataInput{
				Namespace: aws.String(cw.prefix),
				MetricData: []*cloudwatch.MetricDatum{
					{
						MetricName: aws.String(name),
						Dimensions: makeDimensions(g.g.LabelValues()...),
						Value:      aws.Float64(g.g.Value()),
						Timestamp:  aws.Time(now),
					},
				},
			})
			if err != nil {
				cw.logger.Log("during", "WriteLoop", "err", err)
			}
		}
	}
}

// Counter is a CloudWatch counter metric.
type Counter struct {
	c *generic.Counter
}

// NewCounter returns a new usable counter metric.
func NewCounter(name string) *Counter {
	return &Counter{
		c: generic.NewCounter(name),
	}
}

// With is a no-op.
func (c *Counter) With(labelValues ...string) metrics.Counter {
	return c.c.With(labelValues...)
}

// Add implements counter.
func (c *Counter) Add(delta float64) {
	c.c.Add(delta)
}

// Gauge is a CloudWatch gauge metric.
type Gauge struct {
	g *generic.Gauge
}

// NewGauge returns a new usable gauge metric
func NewGauge(name string) *Gauge {
	return &Gauge{
		g: generic.NewGauge(name),
	}
}

func (g *Gauge) With(labelValues ...string) metrics.Gauge {
	return &Gauge{
		g: g.g.With(labelValues...),
	}
}

func (g *Gauge) Set(value float64) {
	g.g.Set(value)
}

func (g *Gauge) Add(delta float64) {
	g.g.Add(delta)
}

func makeDimensions(labelValues ...string) []*cloudwatch.Dimension {
	dimensions := make([]*cloudwatch.Dimension, len(labelValues)/2)
	for i, j := 0, 0; i < len(labelValues); i, j := i+2, j+1 {
		dimensions[j] = &cloudwatch.Dimension{
			Name:  aws.String(labelValues[i]),
			Value: aws.String(labelValues[i+1]),
		}
	}
	return dimensions
}