// Package generic implements generic versions of each of the metric types. They // can be embedded by other implementations, and converted to specific formats // as necessary. package generic import ( "fmt" "io" "math" "sync" "sync/atomic" "github.com/VividCortex/gohistogram" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/internal/lv" ) // Counter is an in-memory implementation of a Counter. type Counter struct { Name string lvs lv.LabelValues bits uint64 } // NewCounter returns a new, usable Counter. func NewCounter(name string) *Counter { return &Counter{ Name: name, } } // With implements Counter. func (c *Counter) With(labelValues ...string) metrics.Counter { return &Counter{ Name: c.Name, bits: atomic.LoadUint64(&c.bits), lvs: c.lvs.With(labelValues...), } } // Add implements Counter. func (c *Counter) Add(delta float64) { for { var ( old = atomic.LoadUint64(&c.bits) newf = math.Float64frombits(old) + delta new = math.Float64bits(newf) ) if atomic.CompareAndSwapUint64(&c.bits, old, new) { break } } } // Value returns the current value of the counter. func (c *Counter) Value() float64 { return math.Float64frombits(atomic.LoadUint64(&c.bits)) } // ValueReset returns the current value of the counter, and resets it to zero. // This is useful for metrics backends whose counter aggregations expect deltas, // like Graphite. func (c *Counter) ValueReset() float64 { for { var ( old = atomic.LoadUint64(&c.bits) newf = 0.0 new = math.Float64bits(newf) ) if atomic.CompareAndSwapUint64(&c.bits, old, new) { return math.Float64frombits(old) } } } // LabelValues returns the set of label values attached to the counter. func (c *Counter) LabelValues() []string { return c.lvs } // Gauge is an in-memory implementation of a Gauge. type Gauge struct { Name string lvs lv.LabelValues bits uint64 } // NewGauge returns a new, usable Gauge. func NewGauge(name string) *Gauge { return &Gauge{ Name: name, } } // With implements Gauge. func (g *Gauge) With(labelValues ...string) metrics.Gauge { return &Gauge{ Name: g.Name, bits: atomic.LoadUint64(&g.bits), lvs: g.lvs.With(labelValues...), } } // Set implements Gauge. func (g *Gauge) Set(value float64) { atomic.StoreUint64(&g.bits, math.Float64bits(value)) } // Add implements metrics.Gauge. func (g *Gauge) Add(delta float64) { for { var ( old = atomic.LoadUint64(&g.bits) newf = math.Float64frombits(old) + delta new = math.Float64bits(newf) ) if atomic.CompareAndSwapUint64(&g.bits, old, new) { break } } } // Value returns the current value of the gauge. func (g *Gauge) Value() float64 { return math.Float64frombits(atomic.LoadUint64(&g.bits)) } // LabelValues returns the set of label values attached to the gauge. func (g *Gauge) LabelValues() []string { return g.lvs } // Histogram is an in-memory implementation of a streaming histogram, based on // VividCortex/gohistogram. It dynamically computes quantiles, so it's not // suitable for aggregation. type Histogram struct { Name string lvs lv.LabelValues h *safeHistogram } // NewHistogram returns a numeric histogram based on VividCortex/gohistogram. A // good default value for buckets is 50. func NewHistogram(name string, buckets int) *Histogram { return &Histogram{ Name: name, h: &safeHistogram{Histogram: gohistogram.NewHistogram(buckets)}, } } // With implements Histogram. func (h *Histogram) With(labelValues ...string) metrics.Histogram { return &Histogram{ Name: h.Name, lvs: h.lvs.With(labelValues...), h: h.h, } } // Observe implements Histogram. func (h *Histogram) Observe(value float64) { h.h.Lock() defer h.h.Unlock() h.h.Add(value) } // Quantile returns the value of the quantile q, 0.0 < q < 1.0. func (h *Histogram) Quantile(q float64) float64 { h.h.RLock() defer h.h.RUnlock() return h.h.Quantile(q) } // LabelValues returns the set of label values attached to the histogram. func (h *Histogram) LabelValues() []string { return h.lvs } // Print writes a string representation of the histogram to the passed writer. // Useful for printing to a terminal. func (h *Histogram) Print(w io.Writer) { h.h.RLock() defer h.h.RUnlock() fmt.Fprintf(w, h.h.String()) } // safeHistogram exists as gohistogram.Histogram is not goroutine-safe. type safeHistogram struct { sync.RWMutex gohistogram.Histogram } // Bucket is a range in a histogram which aggregates observations. type Bucket struct { From, To, Count int64 } // Quantile is a pair of a quantile (0..100) and its observed maximum value. type Quantile struct { Quantile int // 0..100 Value int64 } // SimpleHistogram is an in-memory implementation of a Histogram. It only tracks // an approximate moving average, so is likely too naïve for many use cases. type SimpleHistogram struct { mtx sync.RWMutex lvs lv.LabelValues avg float64 n uint64 } // NewSimpleHistogram returns a SimpleHistogram, ready for observations. func NewSimpleHistogram() *SimpleHistogram { return &SimpleHistogram{} } // With implements Histogram. func (h *SimpleHistogram) With(labelValues ...string) metrics.Histogram { return &SimpleHistogram{ lvs: h.lvs.With(labelValues...), avg: h.avg, n: h.n, } } // Observe implements Histogram. func (h *SimpleHistogram) Observe(value float64) { h.mtx.Lock() defer h.mtx.Unlock() h.n++ h.avg -= h.avg / float64(h.n) h.avg += value / float64(h.n) } // ApproximateMovingAverage returns the approximate moving average of observations. func (h *SimpleHistogram) ApproximateMovingAverage() float64 { h.mtx.RLock() defer h.mtx.RUnlock() return h.avg } // LabelValues returns the set of label values attached to the histogram. func (h *SimpleHistogram) LabelValues() []string { return h.lvs }