2017-02-07 21:33:23 +00:00
|
|
|
package memmetrics
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/codahale/hdrhistogram"
|
|
|
|
"github.com/mailgun/timetools"
|
|
|
|
)
|
|
|
|
|
|
|
|
// HDRHistogram is a tiny wrapper around github.com/codahale/hdrhistogram that provides convenience functions for measuring http latencies
|
|
|
|
type HDRHistogram struct {
|
|
|
|
// lowest trackable value
|
|
|
|
low int64
|
|
|
|
// highest trackable value
|
|
|
|
high int64
|
|
|
|
// significant figures
|
|
|
|
sigfigs int
|
|
|
|
|
|
|
|
h *hdrhistogram.Histogram
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// NewHDRHistogram creates a new HDRHistogram
|
2017-02-07 21:33:23 +00:00
|
|
|
func NewHDRHistogram(low, high int64, sigfigs int) (h *HDRHistogram, err error) {
|
|
|
|
defer func() {
|
|
|
|
if msg := recover(); msg != nil {
|
|
|
|
err = fmt.Errorf("%s", msg)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return &HDRHistogram{
|
|
|
|
low: low,
|
|
|
|
high: high,
|
|
|
|
sigfigs: sigfigs,
|
|
|
|
h: hdrhistogram.New(low, high, sigfigs),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// Export export a HDRHistogram
|
|
|
|
func (h *HDRHistogram) Export() *HDRHistogram {
|
|
|
|
var hist *hdrhistogram.Histogram
|
|
|
|
if h.h != nil {
|
|
|
|
snapshot := h.h.Export()
|
2017-11-22 17:20:03 +00:00
|
|
|
hist = hdrhistogram.Import(snapshot)
|
|
|
|
}
|
2018-08-20 08:38:03 +00:00
|
|
|
return &HDRHistogram{low: h.low, high: h.high, sigfigs: h.sigfigs, h: hist}
|
2017-11-22 17:20:03 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// LatencyAtQuantile sets latency at quantile with microsecond precision
|
2017-02-07 21:33:23 +00:00
|
|
|
func (h *HDRHistogram) LatencyAtQuantile(q float64) time.Duration {
|
|
|
|
return time.Duration(h.ValueAtQuantile(q)) * time.Microsecond
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// RecordLatencies Records latencies with microsecond precision
|
2017-02-07 21:33:23 +00:00
|
|
|
func (h *HDRHistogram) RecordLatencies(d time.Duration, n int64) error {
|
|
|
|
return h.RecordValues(int64(d/time.Microsecond), n)
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// Reset reset a HDRHistogram
|
2017-02-07 21:33:23 +00:00
|
|
|
func (h *HDRHistogram) Reset() {
|
|
|
|
h.h.Reset()
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// ValueAtQuantile sets value at quantile
|
2017-02-07 21:33:23 +00:00
|
|
|
func (h *HDRHistogram) ValueAtQuantile(q float64) int64 {
|
|
|
|
return h.h.ValueAtQuantile(q)
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// RecordValues sets record values
|
2017-02-07 21:33:23 +00:00
|
|
|
func (h *HDRHistogram) RecordValues(v, n int64) error {
|
|
|
|
return h.h.RecordValues(v, n)
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// Merge merge a HDRHistogram
|
2017-02-07 21:33:23 +00:00
|
|
|
func (h *HDRHistogram) Merge(other *HDRHistogram) error {
|
|
|
|
if other == nil {
|
|
|
|
return fmt.Errorf("other is nil")
|
|
|
|
}
|
|
|
|
h.h.Merge(other.h)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type rhOptSetter func(r *RollingHDRHistogram) error
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// RollingClock sets a clock
|
2017-02-07 21:33:23 +00:00
|
|
|
func RollingClock(clock timetools.TimeProvider) rhOptSetter {
|
|
|
|
return func(r *RollingHDRHistogram) error {
|
|
|
|
r.clock = clock
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// RollingHDRHistogram holds multiple histograms and rotates every period.
|
2017-02-07 21:33:23 +00:00
|
|
|
// It provides resulting histogram as a result of a call of 'Merged' function.
|
|
|
|
type RollingHDRHistogram struct {
|
|
|
|
idx int
|
|
|
|
lastRoll time.Time
|
|
|
|
period time.Duration
|
|
|
|
bucketCount int
|
|
|
|
low int64
|
|
|
|
high int64
|
|
|
|
sigfigs int
|
|
|
|
buckets []*HDRHistogram
|
|
|
|
clock timetools.TimeProvider
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// NewRollingHDRHistogram created a new RollingHDRHistogram
|
2017-02-07 21:33:23 +00:00
|
|
|
func NewRollingHDRHistogram(low, high int64, sigfigs int, period time.Duration, bucketCount int, options ...rhOptSetter) (*RollingHDRHistogram, error) {
|
|
|
|
rh := &RollingHDRHistogram{
|
|
|
|
bucketCount: bucketCount,
|
|
|
|
period: period,
|
|
|
|
low: low,
|
|
|
|
high: high,
|
|
|
|
sigfigs: sigfigs,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, o := range options {
|
|
|
|
if err := o(rh); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if rh.clock == nil {
|
|
|
|
rh.clock = &timetools.RealTime{}
|
|
|
|
}
|
|
|
|
|
|
|
|
buckets := make([]*HDRHistogram, rh.bucketCount)
|
|
|
|
for i := range buckets {
|
|
|
|
h, err := NewHDRHistogram(low, high, sigfigs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
buckets[i] = h
|
|
|
|
}
|
|
|
|
rh.buckets = buckets
|
|
|
|
return rh, nil
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// Export export a RollingHDRHistogram
|
2017-11-22 17:20:03 +00:00
|
|
|
func (r *RollingHDRHistogram) Export() *RollingHDRHistogram {
|
|
|
|
export := &RollingHDRHistogram{}
|
|
|
|
export.idx = r.idx
|
|
|
|
export.lastRoll = r.lastRoll
|
|
|
|
export.period = r.period
|
|
|
|
export.bucketCount = r.bucketCount
|
|
|
|
export.low = r.low
|
|
|
|
export.high = r.high
|
|
|
|
export.sigfigs = r.sigfigs
|
|
|
|
export.clock = r.clock
|
|
|
|
|
|
|
|
exportBuckets := make([]*HDRHistogram, len(r.buckets))
|
|
|
|
for i, hist := range r.buckets {
|
|
|
|
exportBuckets[i] = hist.Export()
|
|
|
|
}
|
|
|
|
export.buckets = exportBuckets
|
|
|
|
|
|
|
|
return export
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// Append append a RollingHDRHistogram
|
2017-02-07 21:33:23 +00:00
|
|
|
func (r *RollingHDRHistogram) Append(o *RollingHDRHistogram) error {
|
|
|
|
if r.bucketCount != o.bucketCount || r.period != o.period || r.low != o.low || r.high != o.high || r.sigfigs != o.sigfigs {
|
|
|
|
return fmt.Errorf("can't merge")
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range r.buckets {
|
|
|
|
if err := r.buckets[i].Merge(o.buckets[i]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// Reset reset a RollingHDRHistogram
|
2017-02-07 21:33:23 +00:00
|
|
|
func (r *RollingHDRHistogram) Reset() {
|
|
|
|
r.idx = 0
|
|
|
|
r.lastRoll = r.clock.UtcNow()
|
|
|
|
for _, b := range r.buckets {
|
|
|
|
b.Reset()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RollingHDRHistogram) rotate() {
|
|
|
|
r.idx = (r.idx + 1) % len(r.buckets)
|
|
|
|
r.buckets[r.idx].Reset()
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// Merged gets merged histogram
|
2017-02-07 21:33:23 +00:00
|
|
|
func (r *RollingHDRHistogram) Merged() (*HDRHistogram, error) {
|
|
|
|
m, err := NewHDRHistogram(r.low, r.high, r.sigfigs)
|
|
|
|
if err != nil {
|
|
|
|
return m, err
|
|
|
|
}
|
|
|
|
for _, h := range r.buckets {
|
2017-11-22 17:20:03 +00:00
|
|
|
if errMerge := m.Merge(h); errMerge != nil {
|
|
|
|
return nil, errMerge
|
2017-02-07 21:33:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RollingHDRHistogram) getHist() *HDRHistogram {
|
|
|
|
if r.clock.UtcNow().Sub(r.lastRoll) >= r.period {
|
|
|
|
r.rotate()
|
|
|
|
r.lastRoll = r.clock.UtcNow()
|
|
|
|
}
|
|
|
|
return r.buckets[r.idx]
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// RecordLatencies sets records latencies
|
2017-02-07 21:33:23 +00:00
|
|
|
func (r *RollingHDRHistogram) RecordLatencies(v time.Duration, n int64) error {
|
|
|
|
return r.getHist().RecordLatencies(v, n)
|
|
|
|
}
|
|
|
|
|
2018-08-20 08:38:03 +00:00
|
|
|
// RecordValues set record values
|
2017-02-07 21:33:23 +00:00
|
|
|
func (r *RollingHDRHistogram) RecordValues(v, n int64) error {
|
|
|
|
return r.getHist().RecordValues(v, n)
|
|
|
|
}
|