1 // Package metrics provides convenient facilities to record
2 // on-line high-level performance metrics.
13 "github.com/codahale/hdrhistogram"
16 // Period is the size of a RotatingLatency bucket.
17 // Each RotatingLatency will rotate once per Period.
18 const Period = time.Minute
21 rotatingLatenciesMu sync.Mutex
22 rotatingLatencies []*RotatingLatency
23 latencyExpvar = expvar.NewMap("latency")
26 // PublishLatency publishes rl as an expvar inside the
27 // global latency map (which is itself published under
28 // the key "latency").
29 func PublishLatency(key string, rl *RotatingLatency) {
30 latencyExpvar.Set(key, rl)
33 // A Latency records information about the aggregate latency
34 // of an operation over time.
35 // Internally it holds an HDR histogram (to three significant figures)
36 // and a counter of attempts to record a value
37 // greater than the histogram's max.
39 limit time.Duration // readonly
42 hdr hdrhistogram.Histogram
43 nover int // how many values were over limit
44 max time.Duration // max recorded value (can be over limit)
47 // NewLatency returns a new latency histogram with the given
48 // duration limit and with three significant figures of precision.
49 func NewLatency(limit time.Duration) *Latency {
51 hdr: *hdrhistogram.New(0, int64(limit), 2),
56 // Record attempts to record a duration in the histogram.
57 // If d is greater than the max allowed duration,
58 // it increments a counter instead.
59 func (l *Latency) Record(d time.Duration) {
66 l.hdr.RecordValue(int64(d))
70 // Reset resets l to is original empty state.
71 func (l *Latency) Reset() {
76 // String returns l as a JSON string.
77 // This makes it suitable for use as an expvar.Val.
78 func (l *Latency) String() string {
80 fmt.Fprintf(&b, `{"Histogram":`)
81 h, _ := json.Marshal((&l.hdr).Export()) // #nosec
83 fmt.Fprintf(&b, `,"Over":%d,"Timestamp":%d,"Max":%d}`, l.nover, l.time.Unix(), l.max)
87 // A RotatingLatency holds a rotating circular buffer of Latency objects,
88 // that rotates once per Period time.
89 // It can be used as an expvar Val.
90 // Its exported methods are safe to call concurrently.
91 type RotatingLatency struct {
98 // NewRotatingLatency returns a new rotating latency recorder
99 // with n buckets of history.
100 func NewRotatingLatency(n int, max time.Duration) *RotatingLatency {
101 r := &RotatingLatency{
102 l: make([]Latency, n),
105 r.l[i] = *NewLatency(max)
108 rotatingLatenciesMu.Lock()
109 rotatingLatencies = append(rotatingLatencies, r)
110 rotatingLatenciesMu.Unlock()
114 // Record attempts to record a duration in the current Latency in r.
115 // If d is greater than the max allowed duration,
116 // it increments a counter instead.
117 func (r *RotatingLatency) Record(d time.Duration) {
123 func (r *RotatingLatency) RecordSince(t0 time.Time) {
124 r.Record(time.Since(t0))
127 func (r *RotatingLatency) rotate() {
131 r.cur.time = time.Now()
134 r.cur = &r.l[r.n%len(r.l)]
138 // String returns r as a JSON string.
139 // This makes it suitable for use as an expvar.Val.
149 // "LowestTrackableValue": 0,
150 // "HighestTrackableValue": 1000000000,
151 // "SignificantFigures": 2,
152 // "Counts": [2,0,15,...]
159 // Note that the last bucket is actively recording values.
160 // To collect complete and accurate data over a long time,
161 // store the next-to-last bucket after each rotation.
162 // The last bucket is only useful for a "live" view
163 // with finer granularity than the rotation period (which is one minute).
164 func (r *RotatingLatency) String() string {
168 fmt.Fprintf(&b, `{"Buckets":[`)
173 j := (r.n + i + 1) % len(r.l)
174 fmt.Fprintf(&b, "%s", &r.l[j])
176 fmt.Fprintf(&b, `],"NumRot":%d}`, r.n)
182 for range time.Tick(Period) {
183 rotatingLatenciesMu.Lock()
184 a := rotatingLatencies
185 rotatingLatenciesMu.Unlock()
186 for _, rot := range a {