diff --git a/metrics/cloudwatch/cloudwatch.go b/metrics/cloudwatch/cloudwatch.go index 91055e8..8987dc1 100644 --- a/metrics/cloudwatch/cloudwatch.go +++ b/metrics/cloudwatch/cloudwatch.go @@ -4,6 +4,10 @@ "sync" "time" + + "strconv" + + "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" @@ -14,39 +18,49 @@ // CloudWatch ... type CloudWatch struct { - mtx sync.RWMutex - prefix string - svc *cloudwatch.CloudWatch - counters map[string]*Counter - gauges map[string]*Gauge - logger log.Logger + mtx sync.RWMutex + namespace string + svc *cloudwatch.CloudWatch + counters map[string]*Counter + gauges map[string]*Gauge + histograms map[string]*Histogram + logger log.Logger } // New ... -func New(prefix string, logger log.Logger, svc *cloudwatch.CloudWatch) *CloudWatch { +func New(namespace string, logger log.Logger, svc *cloudwatch.CloudWatch) *CloudWatch { return &CloudWatch{ - prefix: prefix, - svc: svc, - counters: map[string]*Counter{}, - gauges: map[string]*Gauge{}, - logger: logger, + namespace: namespace, + svc: svc, + counters: map[string]*Counter{}, + gauges: map[string]*Gauge{}, + histograms: map[string]*Histogram{}, + logger: logger, } } func (cw *CloudWatch) NewCounter(name string) *Counter { c := NewCounter(name) cw.mtx.Lock() - cw.counters[cw.prefix+name] = c + cw.counters[name] = c cw.mtx.Unlock() return c } func (cw *CloudWatch) NewGauge(name string) *Gauge { - c := NewGauge(name) + g := NewGauge(name) cw.mtx.Lock() - cw.counters[cw.prefix+name] = c + cw.gauges[name] = g cw.mtx.Unlock() - return c + return g +} + +func (cw *CloudWatch) NewHistogram(name string, quantiles []float64, buckets int) *Histogram { + h := NewHistogram(name, quantiles, buckets) + cw.mtx.Lock() + cw.histograms[name] = h + cw.mtx.Unlock() + return h } // WriteLoop is a helper method that invokes WriteTo to the passed writer every @@ -59,38 +73,44 @@ defer cw.mtx.RUnlock() now := time.Now() + datums := []*cloudwatch.MetricDatum{} + 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), - }, - }, + datums = append(datums, &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 { + datums = append(datums, &cloudwatch.MetricDatum{ + MetricName: aws.String(name), + Dimensions: makeDimensions(g.g.LabelValues()...), + Value: aws.Float64(g.g.Value()), + Timestamp: aws.Time(now), + }) + } + + for name, h := range cw.histograms { + for _, quantile := range h.quantiles { + quantileStr := strconv.FormatFloat(quantile, 'f', 2, 64) + datums = append(datums, &cloudwatch.MetricDatum{ + MetricName: aws.String(fmt.Sprintf("%s_%s", name, quantileStr)), + Dimensions: makeDimensions(h.h.LabelValues()...), + Value: aws.Float64(h.h.Quantile(quantile)), + Timestamp: aws.Time(now), + }) } } - 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) - } + _, err := cw.svc.PutMetricData(&cloudwatch.PutMetricDataInput{ + Namespace: aws.String(cw.namespace), + MetricData: datums, + }) + if err != nil { + cw.logger.Log("during", "WriteLoop", "err", err) } } } @@ -131,7 +151,7 @@ func (g *Gauge) With(labelValues ...string) metrics.Gauge { return &Gauge{ - g: g.g.With(labelValues...), + g: g.g.With(labelValues...).(*generic.Gauge), } } @@ -141,6 +161,30 @@ func (g *Gauge) Add(delta float64) { g.g.Add(delta) +} + +// Histogram is a CloudWatch histogram metric +type Histogram struct { + quantiles []float64 + h *generic.Histogram +} + +// NewHistogram returns a new usable histogram metric +func NewHistogram(name string, quantiles []float64, buckets int) *Histogram { + return &Histogram{ + quantiles: quantiles, + h: generic.NewHistogram(name, buckets), + } +} + +func (h *Histogram) With(labelValues ...string) metrics.Histogram { + return &Histogram{ + h: h.h.With(labelValues...).(*generic.Histogram), + } +} + +func (h *Histogram) Observe(value float64) { + h.h.Observe(value) } func makeDimensions(labelValues ...string) []*cloudwatch.Dimension {