package stats import ( "encoding/json" "math" "sort" "strconv" "sync/atomic" ) // Metric is a single meter (counter, gauge or histogram, optionally - with history) type Metric interface { Add(n float64) String() string } // metric is an extended private interface with some additional internal // methods used by timeseries. Counters, gauges and histograms implement it. type metric interface { Metric Reset() Aggregate(roll int, samples []metric) } type multimetric []*timeseries func (mm multimetric) Add(n float64) { for _, m := range mm { m.Add(n) } } func (mm multimetric) MarshalJSON() ([]byte, error) { b := []byte(`{"metrics":[`) for i, m := range mm { if i != 0 { b = append(b, ',') } x, _ := json.Marshal(m) b = append(b, x...) } b = append(b, ']', '}') return b, nil } func (mm multimetric) String() string { return mm[len(mm)-1].String() } func newMetric(builder func() metric, frames ...string) Metric { if len(frames) == 0 { return builder() } if len(frames) == 1 { return newTimeseries(builder, frames[0]) } mm := multimetric{} for _, frame := range frames { mm = append(mm, newTimeseries(builder, frame)) } sort.Slice(mm, func(i, j int) bool { a, b := mm[i], mm[j] return a.interval.Seconds()*float64(len(a.samples)) < b.interval.Seconds()*float64(len(b.samples)) }) return mm } // NewCounter returns a counter metric that increments the value with each // incoming number. func NewCounter(frames ...string) Metric { return newMetric(func() metric { return &counter{} }, frames...) } type counter struct { count uint64 } func (c *counter) String() string { return strconv.FormatFloat(c.value(), 'g', -1, 64) } func (c *counter) Reset() { atomic.StoreUint64(&c.count, math.Float64bits(0)) } func (c *counter) value() float64 { return math.Float64frombits(atomic.LoadUint64(&c.count)) } func (c *counter) Add(n float64) { for { old := math.Float64frombits(atomic.LoadUint64(&c.count)) new := old + n if atomic.CompareAndSwapUint64(&c.count, math.Float64bits(old), math.Float64bits(new)) { return } } } func (c *counter) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Type string `json:"type"` Count float64 `json:"count"` }{"c", c.value()}) } func (c *counter) Aggregate(roll int, samples []metric) { c.Reset() for _, s := range samples { c.Add(s.(*counter).value()) } }