forked from mirror/go-ethereum
parent
7f92e708c5
commit
7bd71fa800
@ -0,0 +1,9 @@ |
||||
*.[68] |
||||
*.a |
||||
*.out |
||||
*.swp |
||||
_obj |
||||
_testmain.go |
||||
cmd/metrics-bench/metrics-bench |
||||
cmd/metrics-example/metrics-example |
||||
cmd/never-read/never-read |
@ -0,0 +1,29 @@ |
||||
Copyright 2012 Richard Crowley. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
2. Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following |
||||
disclaimer in the documentation and/or other materials provided |
||||
with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY RICHARD CROWLEY ``AS IS'' AND ANY EXPRESS |
||||
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||
DISCLAIMED. IN NO EVENT SHALL RICHARD CROWLEY OR CONTRIBUTORS BE LIABLE |
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
||||
THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
The views and conclusions contained in the software and documentation |
||||
are those of the authors and should not be interpreted as representing |
||||
official policies, either expressed or implied, of Richard Crowley. |
@ -0,0 +1,104 @@ |
||||
go-metrics |
||||
========== |
||||
|
||||
Go port of Coda Hale's Metrics library: <https://github.com/codahale/metrics>. |
||||
|
||||
Documentation: <http://godoc.org/github.com/rcrowley/go-metrics>. |
||||
|
||||
Usage |
||||
----- |
||||
|
||||
Create and update metrics: |
||||
|
||||
```go |
||||
c := metrics.NewCounter() |
||||
metrics.Register("foo", c) |
||||
c.Inc(47) |
||||
|
||||
g := metrics.NewGauge() |
||||
metrics.Register("bar", g) |
||||
g.Update(47) |
||||
|
||||
s := metrics.NewExpDecaySample(1028, 0.015) // or metrics.NewUniformSample(1028) |
||||
h := metrics.NewHistogram(s) |
||||
metrics.Register("baz", h) |
||||
h.Update(47) |
||||
|
||||
m := metrics.NewMeter() |
||||
metrics.Register("quux", m) |
||||
m.Mark(47) |
||||
|
||||
t := metrics.NewTimer() |
||||
metrics.Register("bang", t) |
||||
t.Time(func() {}) |
||||
t.Update(47) |
||||
``` |
||||
|
||||
Periodically log every metric in human-readable form to standard error: |
||||
|
||||
```go |
||||
go metrics.Log(metrics.DefaultRegistry, 60e9, log.New(os.Stderr, "metrics: ", log.Lmicroseconds)) |
||||
``` |
||||
|
||||
Periodically log every metric in slightly-more-parseable form to syslog: |
||||
|
||||
```go |
||||
w, _ := syslog.Dial("unixgram", "/dev/log", syslog.LOG_INFO, "metrics") |
||||
go metrics.Syslog(metrics.DefaultRegistry, 60e9, w) |
||||
``` |
||||
|
||||
Periodically emit every metric to Graphite: |
||||
|
||||
```go |
||||
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:2003") |
||||
go metrics.Graphite(metrics.DefaultRegistry, 10e9, "metrics", addr) |
||||
``` |
||||
|
||||
Periodically emit every metric into InfluxDB: |
||||
|
||||
```go |
||||
import "github.com/rcrowley/go-metrics/influxdb" |
||||
|
||||
go influxdb.Influxdb(metrics.DefaultRegistry, 10e9, &influxdb.Config{ |
||||
Host: "127.0.0.1:8086", |
||||
Database: "metrics", |
||||
Username: "test", |
||||
Password: "test", |
||||
}) |
||||
``` |
||||
|
||||
Periodically upload every metric to Librato: |
||||
|
||||
```go |
||||
import "github.com/rcrowley/go-metrics/librato" |
||||
|
||||
go librato.Librato(metrics.DefaultRegistry, |
||||
10e9, // interval |
||||
"example@example.com", // account owner email address |
||||
"token", // Librato API token |
||||
"hostname", // source |
||||
[]float64{0.95}, // precentiles to send |
||||
time.Millisecond, // time unit |
||||
) |
||||
``` |
||||
|
||||
Periodically emit every metric to StatHat: |
||||
|
||||
```go |
||||
import "github.com/rcrowley/go-metrics/stathat" |
||||
|
||||
go stathat.Stathat(metrics.DefaultRegistry, 10e9, "example@example.com") |
||||
``` |
||||
|
||||
Installation |
||||
------------ |
||||
|
||||
```sh |
||||
go get github.com/rcrowley/go-metrics |
||||
``` |
||||
|
||||
StatHat support additionally requires their Go client: |
||||
|
||||
```sh |
||||
go get github.com/stathat/go |
||||
``` |
20
Godeps/_workspace/src/github.com/rcrowley/go-metrics/cmd/metrics-bench/metrics-bench.go
generated
vendored
20
Godeps/_workspace/src/github.com/rcrowley/go-metrics/cmd/metrics-bench/metrics-bench.go
generated
vendored
@ -0,0 +1,20 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/rcrowley/go-metrics" |
||||
"time" |
||||
) |
||||
|
||||
func main() { |
||||
r := metrics.NewRegistry() |
||||
for i := 0; i < 10000; i++ { |
||||
r.Register(fmt.Sprintf("counter-%d", i), metrics.NewCounter()) |
||||
r.Register(fmt.Sprintf("gauge-%d", i), metrics.NewGauge()) |
||||
r.Register(fmt.Sprintf("gaugefloat64-%d", i), metrics.NewGaugeFloat64()) |
||||
r.Register(fmt.Sprintf("histogram-uniform-%d", i), metrics.NewHistogram(metrics.NewUniformSample(1028))) |
||||
r.Register(fmt.Sprintf("histogram-exp-%d", i), metrics.NewHistogram(metrics.NewExpDecaySample(1028, 0.015))) |
||||
r.Register(fmt.Sprintf("meter-%d", i), metrics.NewMeter()) |
||||
} |
||||
time.Sleep(600e9) |
||||
} |
154
Godeps/_workspace/src/github.com/rcrowley/go-metrics/cmd/metrics-example/metrics-example.go
generated
vendored
154
Godeps/_workspace/src/github.com/rcrowley/go-metrics/cmd/metrics-example/metrics-example.go
generated
vendored
@ -0,0 +1,154 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"errors" |
||||
"github.com/rcrowley/go-metrics" |
||||
// "github.com/rcrowley/go-metrics/stathat"
|
||||
"log" |
||||
"math/rand" |
||||
"os" |
||||
// "syslog"
|
||||
"time" |
||||
) |
||||
|
||||
const fanout = 10 |
||||
|
||||
func main() { |
||||
|
||||
r := metrics.NewRegistry() |
||||
|
||||
c := metrics.NewCounter() |
||||
r.Register("foo", c) |
||||
for i := 0; i < fanout; i++ { |
||||
go func() { |
||||
for { |
||||
c.Dec(19) |
||||
time.Sleep(300e6) |
||||
} |
||||
}() |
||||
go func() { |
||||
for { |
||||
c.Inc(47) |
||||
time.Sleep(400e6) |
||||
} |
||||
}() |
||||
} |
||||
|
||||
g := metrics.NewGauge() |
||||
r.Register("bar", g) |
||||
for i := 0; i < fanout; i++ { |
||||
go func() { |
||||
for { |
||||
g.Update(19) |
||||
time.Sleep(300e6) |
||||
} |
||||
}() |
||||
go func() { |
||||
for { |
||||
g.Update(47) |
||||
time.Sleep(400e6) |
||||
} |
||||
}() |
||||
} |
||||
|
||||
gf := metrics.NewGaugeFloat64() |
||||
r.Register("barfloat64", gf) |
||||
for i := 0; i < fanout; i++ { |
||||
go func() { |
||||
for { |
||||
g.Update(19.0) |
||||
time.Sleep(300e6) |
||||
} |
||||
}() |
||||
go func() { |
||||
for { |
||||
g.Update(47.0) |
||||
time.Sleep(400e6) |
||||
} |
||||
}() |
||||
} |
||||
|
||||
hc := metrics.NewHealthcheck(func(h metrics.Healthcheck) { |
||||
if 0 < rand.Intn(2) { |
||||
h.Healthy() |
||||
} else { |
||||
h.Unhealthy(errors.New("baz")) |
||||
} |
||||
}) |
||||
r.Register("baz", hc) |
||||
|
||||
s := metrics.NewExpDecaySample(1028, 0.015) |
||||
//s := metrics.NewUniformSample(1028)
|
||||
h := metrics.NewHistogram(s) |
||||
r.Register("bang", h) |
||||
for i := 0; i < fanout; i++ { |
||||
go func() { |
||||
for { |
||||
h.Update(19) |
||||
time.Sleep(300e6) |
||||
} |
||||
}() |
||||
go func() { |
||||
for { |
||||
h.Update(47) |
||||
time.Sleep(400e6) |
||||
} |
||||
}() |
||||
} |
||||
|
||||
m := metrics.NewMeter() |
||||
r.Register("quux", m) |
||||
for i := 0; i < fanout; i++ { |
||||
go func() { |
||||
for { |
||||
m.Mark(19) |
||||
time.Sleep(300e6) |
||||
} |
||||
}() |
||||
go func() { |
||||
for { |
||||
m.Mark(47) |
||||
time.Sleep(400e6) |
||||
} |
||||
}() |
||||
} |
||||
|
||||
t := metrics.NewTimer() |
||||
r.Register("hooah", t) |
||||
for i := 0; i < fanout; i++ { |
||||
go func() { |
||||
for { |
||||
t.Time(func() { time.Sleep(300e6) }) |
||||
} |
||||
}() |
||||
go func() { |
||||
for { |
||||
t.Time(func() { time.Sleep(400e6) }) |
||||
} |
||||
}() |
||||
} |
||||
|
||||
metrics.RegisterDebugGCStats(r) |
||||
go metrics.CaptureDebugGCStats(r, 5e9) |
||||
|
||||
metrics.RegisterRuntimeMemStats(r) |
||||
go metrics.CaptureRuntimeMemStats(r, 5e9) |
||||
|
||||
metrics.Log(r, 60e9, log.New(os.Stderr, "metrics: ", log.Lmicroseconds)) |
||||
|
||||
/* |
||||
w, err := syslog.Dial("unixgram", "/dev/log", syslog.LOG_INFO, "metrics") |
||||
if nil != err { log.Fatalln(err) } |
||||
metrics.Syslog(r, 60e9, w) |
||||
*/ |
||||
|
||||
/* |
||||
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:2003") |
||||
metrics.Graphite(r, 10e9, "metrics", addr) |
||||
*/ |
||||
|
||||
/* |
||||
stathat.Stathat(r, 10e9, "example@example.com") |
||||
*/ |
||||
|
||||
} |
22
Godeps/_workspace/src/github.com/rcrowley/go-metrics/cmd/never-read/never-read.go
generated
vendored
22
Godeps/_workspace/src/github.com/rcrowley/go-metrics/cmd/never-read/never-read.go
generated
vendored
@ -0,0 +1,22 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
"net" |
||||
) |
||||
|
||||
func main() { |
||||
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:2003") |
||||
l, err := net.ListenTCP("tcp", addr) |
||||
if nil != err { |
||||
log.Fatalln(err) |
||||
} |
||||
log.Println("listening", l.Addr()) |
||||
for { |
||||
c, err := l.AcceptTCP() |
||||
if nil != err { |
||||
log.Fatalln(err) |
||||
} |
||||
log.Println("accepted", c.RemoteAddr()) |
||||
} |
||||
} |
@ -0,0 +1,112 @@ |
||||
package metrics |
||||
|
||||
import "sync/atomic" |
||||
|
||||
// Counters hold an int64 value that can be incremented and decremented.
|
||||
type Counter interface { |
||||
Clear() |
||||
Count() int64 |
||||
Dec(int64) |
||||
Inc(int64) |
||||
Snapshot() Counter |
||||
} |
||||
|
||||
// GetOrRegisterCounter returns an existing Counter or constructs and registers
|
||||
// a new StandardCounter.
|
||||
func GetOrRegisterCounter(name string, r Registry) Counter { |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
return r.GetOrRegister(name, NewCounter).(Counter) |
||||
} |
||||
|
||||
// NewCounter constructs a new StandardCounter.
|
||||
func NewCounter() Counter { |
||||
if UseNilMetrics { |
||||
return NilCounter{} |
||||
} |
||||
return &StandardCounter{0} |
||||
} |
||||
|
||||
// NewRegisteredCounter constructs and registers a new StandardCounter.
|
||||
func NewRegisteredCounter(name string, r Registry) Counter { |
||||
c := NewCounter() |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
r.Register(name, c) |
||||
return c |
||||
} |
||||
|
||||
// CounterSnapshot is a read-only copy of another Counter.
|
||||
type CounterSnapshot int64 |
||||
|
||||
// Clear panics.
|
||||
func (CounterSnapshot) Clear() { |
||||
panic("Clear called on a CounterSnapshot") |
||||
} |
||||
|
||||
// Count returns the count at the time the snapshot was taken.
|
||||
func (c CounterSnapshot) Count() int64 { return int64(c) } |
||||
|
||||
// Dec panics.
|
||||
func (CounterSnapshot) Dec(int64) { |
||||
panic("Dec called on a CounterSnapshot") |
||||
} |
||||
|
||||
// Inc panics.
|
||||
func (CounterSnapshot) Inc(int64) { |
||||
panic("Inc called on a CounterSnapshot") |
||||
} |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (c CounterSnapshot) Snapshot() Counter { return c } |
||||
|
||||
// NilCounter is a no-op Counter.
|
||||
type NilCounter struct{} |
||||
|
||||
// Clear is a no-op.
|
||||
func (NilCounter) Clear() {} |
||||
|
||||
// Count is a no-op.
|
||||
func (NilCounter) Count() int64 { return 0 } |
||||
|
||||
// Dec is a no-op.
|
||||
func (NilCounter) Dec(i int64) {} |
||||
|
||||
// Inc is a no-op.
|
||||
func (NilCounter) Inc(i int64) {} |
||||
|
||||
// Snapshot is a no-op.
|
||||
func (NilCounter) Snapshot() Counter { return NilCounter{} } |
||||
|
||||
// StandardCounter is the standard implementation of a Counter and uses the
|
||||
// sync/atomic package to manage a single int64 value.
|
||||
type StandardCounter struct { |
||||
count int64 |
||||
} |
||||
|
||||
// Clear sets the counter to zero.
|
||||
func (c *StandardCounter) Clear() { |
||||
atomic.StoreInt64(&c.count, 0) |
||||
} |
||||
|
||||
// Count returns the current count.
|
||||
func (c *StandardCounter) Count() int64 { |
||||
return atomic.LoadInt64(&c.count) |
||||
} |
||||
|
||||
// Dec decrements the counter by the given amount.
|
||||
func (c *StandardCounter) Dec(i int64) { |
||||
atomic.AddInt64(&c.count, -i) |
||||
} |
||||
|
||||
// Inc increments the counter by the given amount.
|
||||
func (c *StandardCounter) Inc(i int64) { |
||||
atomic.AddInt64(&c.count, i) |
||||
} |
||||
|
||||
// Snapshot returns a read-only copy of the counter.
|
||||
func (c *StandardCounter) Snapshot() Counter { |
||||
return CounterSnapshot(c.Count()) |
||||
} |
@ -0,0 +1,77 @@ |
||||
package metrics |
||||
|
||||
import "testing" |
||||
|
||||
func BenchmarkCounter(b *testing.B) { |
||||
c := NewCounter() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
c.Inc(1) |
||||
} |
||||
} |
||||
|
||||
func TestCounterClear(t *testing.T) { |
||||
c := NewCounter() |
||||
c.Inc(1) |
||||
c.Clear() |
||||
if count := c.Count(); 0 != count { |
||||
t.Errorf("c.Count(): 0 != %v\n", count) |
||||
} |
||||
} |
||||
|
||||
func TestCounterDec1(t *testing.T) { |
||||
c := NewCounter() |
||||
c.Dec(1) |
||||
if count := c.Count(); -1 != count { |
||||
t.Errorf("c.Count(): -1 != %v\n", count) |
||||
} |
||||
} |
||||
|
||||
func TestCounterDec2(t *testing.T) { |
||||
c := NewCounter() |
||||
c.Dec(2) |
||||
if count := c.Count(); -2 != count { |
||||
t.Errorf("c.Count(): -2 != %v\n", count) |
||||
} |
||||
} |
||||
|
||||
func TestCounterInc1(t *testing.T) { |
||||
c := NewCounter() |
||||
c.Inc(1) |
||||
if count := c.Count(); 1 != count { |
||||
t.Errorf("c.Count(): 1 != %v\n", count) |
||||
} |
||||
} |
||||
|
||||
func TestCounterInc2(t *testing.T) { |
||||
c := NewCounter() |
||||
c.Inc(2) |
||||
if count := c.Count(); 2 != count { |
||||
t.Errorf("c.Count(): 2 != %v\n", count) |
||||
} |
||||
} |
||||
|
||||
func TestCounterSnapshot(t *testing.T) { |
||||
c := NewCounter() |
||||
c.Inc(1) |
||||
snapshot := c.Snapshot() |
||||
c.Inc(1) |
||||
if count := snapshot.Count(); 1 != count { |
||||
t.Errorf("c.Count(): 1 != %v\n", count) |
||||
} |
||||
} |
||||
|
||||
func TestCounterZero(t *testing.T) { |
||||
c := NewCounter() |
||||
if count := c.Count(); 0 != count { |
||||
t.Errorf("c.Count(): 0 != %v\n", count) |
||||
} |
||||
} |
||||
|
||||
func TestGetOrRegisterCounter(t *testing.T) { |
||||
r := NewRegistry() |
||||
NewRegisteredCounter("foo", r).Inc(47) |
||||
if c := GetOrRegisterCounter("foo", r); 47 != c.Count() { |
||||
t.Fatal(c) |
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"runtime/debug" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
debugMetrics struct { |
||||
GCStats struct { |
||||
LastGC Gauge |
||||
NumGC Gauge |
||||
Pause Histogram |
||||
//PauseQuantiles Histogram
|
||||
PauseTotal Gauge |
||||
} |
||||
ReadGCStats Timer |
||||
} |
||||
gcStats debug.GCStats |
||||
) |
||||
|
||||
// Capture new values for the Go garbage collector statistics exported in
|
||||
// debug.GCStats. This is designed to be called as a goroutine.
|
||||
func CaptureDebugGCStats(r Registry, d time.Duration) { |
||||
for _ = range time.Tick(d) { |
||||
CaptureDebugGCStatsOnce(r) |
||||
} |
||||
} |
||||
|
||||
// Capture new values for the Go garbage collector statistics exported in
|
||||
// debug.GCStats. This is designed to be called in a background goroutine.
|
||||
// Giving a registry which has not been given to RegisterDebugGCStats will
|
||||
// panic.
|
||||
//
|
||||
// Be careful (but much less so) with this because debug.ReadGCStats calls
|
||||
// the C function runtime·lock(runtime·mheap) which, while not a stop-the-world
|
||||
// operation, isn't something you want to be doing all the time.
|
||||
func CaptureDebugGCStatsOnce(r Registry) { |
||||
lastGC := gcStats.LastGC |
||||
t := time.Now() |
||||
debug.ReadGCStats(&gcStats) |
||||
debugMetrics.ReadGCStats.UpdateSince(t) |
||||
|
||||
debugMetrics.GCStats.LastGC.Update(int64(gcStats.LastGC.UnixNano())) |
||||
debugMetrics.GCStats.NumGC.Update(int64(gcStats.NumGC)) |
||||
if lastGC != gcStats.LastGC && 0 < len(gcStats.Pause) { |
||||
debugMetrics.GCStats.Pause.Update(int64(gcStats.Pause[0])) |
||||
} |
||||
//debugMetrics.GCStats.PauseQuantiles.Update(gcStats.PauseQuantiles)
|
||||
debugMetrics.GCStats.PauseTotal.Update(int64(gcStats.PauseTotal)) |
||||
} |
||||
|
||||
// Register metrics for the Go garbage collector statistics exported in
|
||||
// debug.GCStats. The metrics are named by their fully-qualified Go symbols,
|
||||
// i.e. debug.GCStats.PauseTotal.
|
||||
func RegisterDebugGCStats(r Registry) { |
||||
debugMetrics.GCStats.LastGC = NewGauge() |
||||
debugMetrics.GCStats.NumGC = NewGauge() |
||||
debugMetrics.GCStats.Pause = NewHistogram(NewExpDecaySample(1028, 0.015)) |
||||
//debugMetrics.GCStats.PauseQuantiles = NewHistogram(NewExpDecaySample(1028, 0.015))
|
||||
debugMetrics.GCStats.PauseTotal = NewGauge() |
||||
debugMetrics.ReadGCStats = NewTimer() |
||||
|
||||
r.Register("debug.GCStats.LastGC", debugMetrics.GCStats.LastGC) |
||||
r.Register("debug.GCStats.NumGC", debugMetrics.GCStats.NumGC) |
||||
r.Register("debug.GCStats.Pause", debugMetrics.GCStats.Pause) |
||||
//r.Register("debug.GCStats.PauseQuantiles", debugMetrics.GCStats.PauseQuantiles)
|
||||
r.Register("debug.GCStats.PauseTotal", debugMetrics.GCStats.PauseTotal) |
||||
r.Register("debug.ReadGCStats", debugMetrics.ReadGCStats) |
||||
} |
||||
|
||||
// Allocate an initial slice for gcStats.Pause to avoid allocations during
|
||||
// normal operation.
|
||||
func init() { |
||||
gcStats.Pause = make([]time.Duration, 11) |
||||
} |
@ -0,0 +1,48 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"runtime" |
||||
"runtime/debug" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func BenchmarkDebugGCStats(b *testing.B) { |
||||
r := NewRegistry() |
||||
RegisterDebugGCStats(r) |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
CaptureDebugGCStatsOnce(r) |
||||
} |
||||
} |
||||
|
||||
func TestDebugGCStatsBlocking(t *testing.T) { |
||||
if g := runtime.GOMAXPROCS(0); g < 2 { |
||||
t.Skipf("skipping TestDebugGCMemStatsBlocking with GOMAXPROCS=%d\n", g) |
||||
return |
||||
} |
||||
ch := make(chan int) |
||||
go testDebugGCStatsBlocking(ch) |
||||
var gcStats debug.GCStats |
||||
t0 := time.Now() |
||||
debug.ReadGCStats(&gcStats) |
||||
t1 := time.Now() |
||||
t.Log("i++ during debug.ReadGCStats:", <-ch) |
||||
go testDebugGCStatsBlocking(ch) |
||||
d := t1.Sub(t0) |
||||
t.Log(d) |
||||
time.Sleep(d) |
||||
t.Log("i++ during time.Sleep:", <-ch) |
||||
} |
||||
|
||||
func testDebugGCStatsBlocking(ch chan int) { |
||||
i := 0 |
||||
for { |
||||
select { |
||||
case ch <- i: |
||||
return |
||||
default: |
||||
i++ |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,118 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"math" |
||||
"sync" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// EWMAs continuously calculate an exponentially-weighted moving average
|
||||
// based on an outside source of clock ticks.
|
||||
type EWMA interface { |
||||
Rate() float64 |
||||
Snapshot() EWMA |
||||
Tick() |
||||
Update(int64) |
||||
} |
||||
|
||||
// NewEWMA constructs a new EWMA with the given alpha.
|
||||
func NewEWMA(alpha float64) EWMA { |
||||
if UseNilMetrics { |
||||
return NilEWMA{} |
||||
} |
||||
return &StandardEWMA{alpha: alpha} |
||||
} |
||||
|
||||
// NewEWMA1 constructs a new EWMA for a one-minute moving average.
|
||||
func NewEWMA1() EWMA { |
||||
return NewEWMA(1 - math.Exp(-5.0/60.0/1)) |
||||
} |
||||
|
||||
// NewEWMA5 constructs a new EWMA for a five-minute moving average.
|
||||
func NewEWMA5() EWMA { |
||||
return NewEWMA(1 - math.Exp(-5.0/60.0/5)) |
||||
} |
||||
|
||||
// NewEWMA15 constructs a new EWMA for a fifteen-minute moving average.
|
||||
func NewEWMA15() EWMA { |
||||
return NewEWMA(1 - math.Exp(-5.0/60.0/15)) |
||||
} |
||||
|
||||
// EWMASnapshot is a read-only copy of another EWMA.
|
||||
type EWMASnapshot float64 |
||||
|
||||
// Rate returns the rate of events per second at the time the snapshot was
|
||||
// taken.
|
||||
func (a EWMASnapshot) Rate() float64 { return float64(a) } |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (a EWMASnapshot) Snapshot() EWMA { return a } |
||||
|
||||
// Tick panics.
|
||||
func (EWMASnapshot) Tick() { |
||||
panic("Tick called on an EWMASnapshot") |
||||
} |
||||
|
||||
// Update panics.
|
||||
func (EWMASnapshot) Update(int64) { |
||||
panic("Update called on an EWMASnapshot") |
||||
} |
||||
|
||||
// NilEWMA is a no-op EWMA.
|
||||
type NilEWMA struct{} |
||||
|
||||
// Rate is a no-op.
|
||||
func (NilEWMA) Rate() float64 { return 0.0 } |
||||
|
||||
// Snapshot is a no-op.
|
||||
func (NilEWMA) Snapshot() EWMA { return NilEWMA{} } |
||||
|
||||
// Tick is a no-op.
|
||||
func (NilEWMA) Tick() {} |
||||
|
||||
// Update is a no-op.
|
||||
func (NilEWMA) Update(n int64) {} |
||||
|
||||
// StandardEWMA is the standard implementation of an EWMA and tracks the number
|
||||
// of uncounted events and processes them on each tick. It uses the
|
||||
// sync/atomic package to manage uncounted events.
|
||||
type StandardEWMA struct { |
||||
uncounted int64 // /!\ this should be the first member to ensure 64-bit alignment
|
||||
alpha float64 |
||||
rate float64 |
||||
init bool |
||||
mutex sync.Mutex |
||||
} |
||||
|
||||
// Rate returns the moving average rate of events per second.
|
||||
func (a *StandardEWMA) Rate() float64 { |
||||
a.mutex.Lock() |
||||
defer a.mutex.Unlock() |
||||
return a.rate * float64(1e9) |
||||
} |
||||
|
||||
// Snapshot returns a read-only copy of the EWMA.
|
||||
func (a *StandardEWMA) Snapshot() EWMA { |
||||
return EWMASnapshot(a.Rate()) |
||||
} |
||||
|
||||
// Tick ticks the clock to update the moving average. It assumes it is called
|
||||
// every five seconds.
|
||||
func (a *StandardEWMA) Tick() { |
||||
count := atomic.LoadInt64(&a.uncounted) |
||||
atomic.AddInt64(&a.uncounted, -count) |
||||
instantRate := float64(count) / float64(5e9) |
||||
a.mutex.Lock() |
||||
defer a.mutex.Unlock() |
||||
if a.init { |
||||
a.rate += a.alpha * (instantRate - a.rate) |
||||
} else { |
||||
a.init = true |
||||
a.rate = instantRate |
||||
} |
||||
} |
||||
|
||||
// Update adds n uncounted events.
|
||||
func (a *StandardEWMA) Update(n int64) { |
||||
atomic.AddInt64(&a.uncounted, n) |
||||
} |
@ -0,0 +1,225 @@ |
||||
package metrics |
||||
|
||||
import "testing" |
||||
|
||||
func BenchmarkEWMA(b *testing.B) { |
||||
a := NewEWMA1() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
a.Update(1) |
||||
a.Tick() |
||||
} |
||||
} |
||||
|
||||
func TestEWMA1(t *testing.T) { |
||||
a := NewEWMA1() |
||||
a.Update(3) |
||||
a.Tick() |
||||
if rate := a.Rate(); 0.6 != rate { |
||||
t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.22072766470286553 != rate { |
||||
t.Errorf("1 minute a.Rate(): 0.22072766470286553 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.08120116994196772 != rate { |
||||
t.Errorf("2 minute a.Rate(): 0.08120116994196772 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.029872241020718428 != rate { |
||||
t.Errorf("3 minute a.Rate(): 0.029872241020718428 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.01098938333324054 != rate { |
||||
t.Errorf("4 minute a.Rate(): 0.01098938333324054 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.004042768199451294 != rate { |
||||
t.Errorf("5 minute a.Rate(): 0.004042768199451294 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.0014872513059998212 != rate { |
||||
t.Errorf("6 minute a.Rate(): 0.0014872513059998212 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.0005471291793327122 != rate { |
||||
t.Errorf("7 minute a.Rate(): 0.0005471291793327122 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.00020127757674150815 != rate { |
||||
t.Errorf("8 minute a.Rate(): 0.00020127757674150815 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 7.404588245200814e-05 != rate { |
||||
t.Errorf("9 minute a.Rate(): 7.404588245200814e-05 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 2.7239957857491083e-05 != rate { |
||||
t.Errorf("10 minute a.Rate(): 2.7239957857491083e-05 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 1.0021020474147462e-05 != rate { |
||||
t.Errorf("11 minute a.Rate(): 1.0021020474147462e-05 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 3.6865274119969525e-06 != rate { |
||||
t.Errorf("12 minute a.Rate(): 3.6865274119969525e-06 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 1.3561976441886433e-06 != rate { |
||||
t.Errorf("13 minute a.Rate(): 1.3561976441886433e-06 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 4.989172314621449e-07 != rate { |
||||
t.Errorf("14 minute a.Rate(): 4.989172314621449e-07 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 1.8354139230109722e-07 != rate { |
||||
t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate) |
||||
} |
||||
} |
||||
|
||||
func TestEWMA5(t *testing.T) { |
||||
a := NewEWMA5() |
||||
a.Update(3) |
||||
a.Tick() |
||||
if rate := a.Rate(); 0.6 != rate { |
||||
t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.49123845184678905 != rate { |
||||
t.Errorf("1 minute a.Rate(): 0.49123845184678905 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.4021920276213837 != rate { |
||||
t.Errorf("2 minute a.Rate(): 0.4021920276213837 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.32928698165641596 != rate { |
||||
t.Errorf("3 minute a.Rate(): 0.32928698165641596 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.269597378470333 != rate { |
||||
t.Errorf("4 minute a.Rate(): 0.269597378470333 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.2207276647028654 != rate { |
||||
t.Errorf("5 minute a.Rate(): 0.2207276647028654 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.18071652714732128 != rate { |
||||
t.Errorf("6 minute a.Rate(): 0.18071652714732128 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.14795817836496392 != rate { |
||||
t.Errorf("7 minute a.Rate(): 0.14795817836496392 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.12113791079679326 != rate { |
||||
t.Errorf("8 minute a.Rate(): 0.12113791079679326 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.09917933293295193 != rate { |
||||
t.Errorf("9 minute a.Rate(): 0.09917933293295193 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.08120116994196763 != rate { |
||||
t.Errorf("10 minute a.Rate(): 0.08120116994196763 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.06648189501740036 != rate { |
||||
t.Errorf("11 minute a.Rate(): 0.06648189501740036 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.05443077197364752 != rate { |
||||
t.Errorf("12 minute a.Rate(): 0.05443077197364752 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.04456414692860035 != rate { |
||||
t.Errorf("13 minute a.Rate(): 0.04456414692860035 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.03648603757513079 != rate { |
||||
t.Errorf("14 minute a.Rate(): 0.03648603757513079 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.0298722410207183831020718428 != rate { |
||||
t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate) |
||||
} |
||||
} |
||||
|
||||
func TestEWMA15(t *testing.T) { |
||||
a := NewEWMA15() |
||||
a.Update(3) |
||||
a.Tick() |
||||
if rate := a.Rate(); 0.6 != rate { |
||||
t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.5613041910189706 != rate { |
||||
t.Errorf("1 minute a.Rate(): 0.5613041910189706 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.5251039914257684 != rate { |
||||
t.Errorf("2 minute a.Rate(): 0.5251039914257684 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.4912384518467888184678905 != rate { |
||||
t.Errorf("3 minute a.Rate(): 0.4912384518467888184678905 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.459557003018789 != rate { |
||||
t.Errorf("4 minute a.Rate(): 0.459557003018789 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.4299187863442732 != rate { |
||||
t.Errorf("5 minute a.Rate(): 0.4299187863442732 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.4021920276213831 != rate { |
||||
t.Errorf("6 minute a.Rate(): 0.4021920276213831 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.37625345116383313 != rate { |
||||
t.Errorf("7 minute a.Rate(): 0.37625345116383313 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.3519877317060185 != rate { |
||||
t.Errorf("8 minute a.Rate(): 0.3519877317060185 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.3292869816564153165641596 != rate { |
||||
t.Errorf("9 minute a.Rate(): 0.3292869816564153165641596 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.3080502714195546 != rate { |
||||
t.Errorf("10 minute a.Rate(): 0.3080502714195546 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.2881831806538789 != rate { |
||||
t.Errorf("11 minute a.Rate(): 0.2881831806538789 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.26959737847033216 != rate { |
||||
t.Errorf("12 minute a.Rate(): 0.26959737847033216 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.2522102307052083 != rate { |
||||
t.Errorf("13 minute a.Rate(): 0.2522102307052083 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.23594443252115815 != rate { |
||||
t.Errorf("14 minute a.Rate(): 0.23594443252115815 != %v\n", rate) |
||||
} |
||||
elapseMinute(a) |
||||
if rate := a.Rate(); 0.2207276647028646247028654470286553 != rate { |
||||
t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate) |
||||
} |
||||
} |
||||
|
||||
func elapseMinute(a EWMA) { |
||||
for i := 0; i < 12; i++ { |
||||
a.Tick() |
||||
} |
||||
} |
@ -0,0 +1,84 @@ |
||||
package metrics |
||||
|
||||
import "sync/atomic" |
||||
|
||||
// Gauges hold an int64 value that can be set arbitrarily.
|
||||
type Gauge interface { |
||||
Snapshot() Gauge |
||||
Update(int64) |
||||
Value() int64 |
||||
} |
||||
|
||||
// GetOrRegisterGauge returns an existing Gauge or constructs and registers a
|
||||
// new StandardGauge.
|
||||
func GetOrRegisterGauge(name string, r Registry) Gauge { |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
return r.GetOrRegister(name, NewGauge).(Gauge) |
||||
} |
||||
|
||||
// NewGauge constructs a new StandardGauge.
|
||||
func NewGauge() Gauge { |
||||
if UseNilMetrics { |
||||
return NilGauge{} |
||||
} |
||||
return &StandardGauge{0} |
||||
} |
||||
|
||||
// NewRegisteredGauge constructs and registers a new StandardGauge.
|
||||
func NewRegisteredGauge(name string, r Registry) Gauge { |
||||
c := NewGauge() |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
r.Register(name, c) |
||||
return c |
||||
} |
||||
|
||||
// GaugeSnapshot is a read-only copy of another Gauge.
|
||||
type GaugeSnapshot int64 |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (g GaugeSnapshot) Snapshot() Gauge { return g } |
||||
|
||||
// Update panics.
|
||||
func (GaugeSnapshot) Update(int64) { |
||||
panic("Update called on a GaugeSnapshot") |
||||
} |
||||
|
||||
// Value returns the value at the time the snapshot was taken.
|
||||
func (g GaugeSnapshot) Value() int64 { return int64(g) } |
||||
|
||||
// NilGauge is a no-op Gauge.
|
||||
type NilGauge struct{} |
||||
|
||||
// Snapshot is a no-op.
|
||||
func (NilGauge) Snapshot() Gauge { return NilGauge{} } |
||||
|
||||
// Update is a no-op.
|
||||
func (NilGauge) Update(v int64) {} |
||||
|
||||
// Value is a no-op.
|
||||
func (NilGauge) Value() int64 { return 0 } |
||||
|
||||
// StandardGauge is the standard implementation of a Gauge and uses the
|
||||
// sync/atomic package to manage a single int64 value.
|
||||
type StandardGauge struct { |
||||
value int64 |
||||
} |
||||
|
||||
// Snapshot returns a read-only copy of the gauge.
|
||||
func (g *StandardGauge) Snapshot() Gauge { |
||||
return GaugeSnapshot(g.Value()) |
||||
} |
||||
|
||||
// Update updates the gauge's value.
|
||||
func (g *StandardGauge) Update(v int64) { |
||||
atomic.StoreInt64(&g.value, v) |
||||
} |
||||
|
||||
// Value returns the gauge's current value.
|
||||
func (g *StandardGauge) Value() int64 { |
||||
return atomic.LoadInt64(&g.value) |
||||
} |
@ -0,0 +1,91 @@ |
||||
package metrics |
||||
|
||||
import "sync" |
||||
|
||||
// GaugeFloat64s hold a float64 value that can be set arbitrarily.
|
||||
type GaugeFloat64 interface { |
||||
Snapshot() GaugeFloat64 |
||||
Update(float64) |
||||
Value() float64 |
||||
} |
||||
|
||||
// GetOrRegisterGaugeFloat64 returns an existing GaugeFloat64 or constructs and registers a
|
||||
// new StandardGaugeFloat64.
|
||||
func GetOrRegisterGaugeFloat64(name string, r Registry) GaugeFloat64 { |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
return r.GetOrRegister(name, NewGaugeFloat64()).(GaugeFloat64) |
||||
} |
||||
|
||||
// NewGaugeFloat64 constructs a new StandardGaugeFloat64.
|
||||
func NewGaugeFloat64() GaugeFloat64 { |
||||
if UseNilMetrics { |
||||
return NilGaugeFloat64{} |
||||
} |
||||
return &StandardGaugeFloat64{ |
||||
value: 0.0, |
||||
} |
||||
} |
||||
|
||||
// NewRegisteredGaugeFloat64 constructs and registers a new StandardGaugeFloat64.
|
||||
func NewRegisteredGaugeFloat64(name string, r Registry) GaugeFloat64 { |
||||
c := NewGaugeFloat64() |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
r.Register(name, c) |
||||
return c |
||||
} |
||||
|
||||
// GaugeFloat64Snapshot is a read-only copy of another GaugeFloat64.
|
||||
type GaugeFloat64Snapshot float64 |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (g GaugeFloat64Snapshot) Snapshot() GaugeFloat64 { return g } |
||||
|
||||
// Update panics.
|
||||
func (GaugeFloat64Snapshot) Update(float64) { |
||||
panic("Update called on a GaugeFloat64Snapshot") |
||||
} |
||||
|
||||
// Value returns the value at the time the snapshot was taken.
|
||||
func (g GaugeFloat64Snapshot) Value() float64 { return float64(g) } |
||||
|
||||
// NilGauge is a no-op Gauge.
|
||||
type NilGaugeFloat64 struct{} |
||||
|
||||
// Snapshot is a no-op.
|
||||
func (NilGaugeFloat64) Snapshot() GaugeFloat64 { return NilGaugeFloat64{} } |
||||
|
||||
// Update is a no-op.
|
||||
func (NilGaugeFloat64) Update(v float64) {} |
||||
|
||||
// Value is a no-op.
|
||||
func (NilGaugeFloat64) Value() float64 { return 0.0 } |
||||
|
||||
// StandardGaugeFloat64 is the standard implementation of a GaugeFloat64 and uses
|
||||
// sync.Mutex to manage a single float64 value.
|
||||
type StandardGaugeFloat64 struct { |
||||
mutex sync.Mutex |
||||
value float64 |
||||
} |
||||
|
||||
// Snapshot returns a read-only copy of the gauge.
|
||||
func (g *StandardGaugeFloat64) Snapshot() GaugeFloat64 { |
||||
return GaugeFloat64Snapshot(g.Value()) |
||||
} |
||||
|
||||
// Update updates the gauge's value.
|
||||
func (g *StandardGaugeFloat64) Update(v float64) { |
||||
g.mutex.Lock() |
||||
defer g.mutex.Unlock() |
||||
g.value = v |
||||
} |
||||
|
||||
// Value returns the gauge's current value.
|
||||
func (g *StandardGaugeFloat64) Value() float64 { |
||||
g.mutex.Lock() |
||||
defer g.mutex.Unlock() |
||||
return g.value |
||||
} |
@ -0,0 +1,38 @@ |
||||
package metrics |
||||
|
||||
import "testing" |
||||
|
||||
func BenchmarkGuageFloat64(b *testing.B) { |
||||
g := NewGaugeFloat64() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
g.Update(float64(i)) |
||||
} |
||||
} |
||||
|
||||
func TestGaugeFloat64(t *testing.T) { |
||||
g := NewGaugeFloat64() |
||||
g.Update(float64(47.0)) |
||||
if v := g.Value(); float64(47.0) != v { |
||||
t.Errorf("g.Value(): 47.0 != %v\n", v) |
||||
} |
||||
} |
||||
|
||||
func TestGaugeFloat64Snapshot(t *testing.T) { |
||||
g := NewGaugeFloat64() |
||||
g.Update(float64(47.0)) |
||||
snapshot := g.Snapshot() |
||||
g.Update(float64(0)) |
||||
if v := snapshot.Value(); float64(47.0) != v { |
||||
t.Errorf("g.Value(): 47.0 != %v\n", v) |
||||
} |
||||
} |
||||
|
||||
func TestGetOrRegisterGaugeFloat64(t *testing.T) { |
||||
r := NewRegistry() |
||||
NewRegisteredGaugeFloat64("foo", r).Update(float64(47.0)) |
||||
t.Logf("registry: %v", r) |
||||
if g := GetOrRegisterGaugeFloat64("foo", r); float64(47.0) != g.Value() { |
||||
t.Fatal(g) |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
package metrics |
||||
|
||||
import "testing" |
||||
|
||||
func BenchmarkGuage(b *testing.B) { |
||||
g := NewGauge() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
g.Update(int64(i)) |
||||
} |
||||
} |
||||
|
||||
func TestGauge(t *testing.T) { |
||||
g := NewGauge() |
||||
g.Update(int64(47)) |
||||
if v := g.Value(); 47 != v { |
||||
t.Errorf("g.Value(): 47 != %v\n", v) |
||||
} |
||||
} |
||||
|
||||
func TestGaugeSnapshot(t *testing.T) { |
||||
g := NewGauge() |
||||
g.Update(int64(47)) |
||||
snapshot := g.Snapshot() |
||||
g.Update(int64(0)) |
||||
if v := snapshot.Value(); 47 != v { |
||||
t.Errorf("g.Value(): 47 != %v\n", v) |
||||
} |
||||
} |
||||
|
||||
func TestGetOrRegisterGauge(t *testing.T) { |
||||
r := NewRegistry() |
||||
NewRegisteredGauge("foo", r).Update(47) |
||||
if g := GetOrRegisterGauge("foo", r); 47 != g.Value() { |
||||
t.Fatal(g) |
||||
} |
||||
} |
@ -0,0 +1,111 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"log" |
||||
"net" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// GraphiteConfig provides a container with configuration parameters for
|
||||
// the Graphite exporter
|
||||
type GraphiteConfig struct { |
||||
Addr *net.TCPAddr // Network address to connect to
|
||||
Registry Registry // Registry to be exported
|
||||
FlushInterval time.Duration // Flush interval
|
||||
DurationUnit time.Duration // Time conversion unit for durations
|
||||
Prefix string // Prefix to be prepended to metric names
|
||||
Percentiles []float64 // Percentiles to export from timers and histograms
|
||||
} |
||||
|
||||
// Graphite is a blocking exporter function which reports metrics in r
|
||||
// to a graphite server located at addr, flushing them every d duration
|
||||
// and prepending metric names with prefix.
|
||||
func Graphite(r Registry, d time.Duration, prefix string, addr *net.TCPAddr) { |
||||
GraphiteWithConfig(GraphiteConfig{ |
||||
Addr: addr, |
||||
Registry: r, |
||||
FlushInterval: d, |
||||
DurationUnit: time.Nanosecond, |
||||
Prefix: prefix, |
||||
Percentiles: []float64{0.5, 0.75, 0.95, 0.99, 0.999}, |
||||
}) |
||||
} |
||||
|
||||
// GraphiteWithConfig is a blocking exporter function just like Graphite,
|
||||
// but it takes a GraphiteConfig instead.
|
||||
func GraphiteWithConfig(c GraphiteConfig) { |
||||
for _ = range time.Tick(c.FlushInterval) { |
||||
if err := graphite(&c); nil != err { |
||||
log.Println(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// GraphiteOnce performs a single submission to Graphite, returning a
|
||||
// non-nil error on failed connections. This can be used in a loop
|
||||
// similar to GraphiteWithConfig for custom error handling.
|
||||
func GraphiteOnce(c GraphiteConfig) error { |
||||
return graphite(&c) |
||||
} |
||||
|
||||
func graphite(c *GraphiteConfig) error { |
||||
now := time.Now().Unix() |
||||
du := float64(c.DurationUnit) |
||||
conn, err := net.DialTCP("tcp", nil, c.Addr) |
||||
if nil != err { |
||||
return err |
||||
} |
||||
defer conn.Close() |
||||
w := bufio.NewWriter(conn) |
||||
c.Registry.Each(func(name string, i interface{}) { |
||||
switch metric := i.(type) { |
||||
case Counter: |
||||
fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, metric.Count(), now) |
||||
case Gauge: |
||||
fmt.Fprintf(w, "%s.%s.value %d %d\n", c.Prefix, name, metric.Value(), now) |
||||
case GaugeFloat64: |
||||
fmt.Fprintf(w, "%s.%s.value %f %d\n", c.Prefix, name, metric.Value(), now) |
||||
case Histogram: |
||||
h := metric.Snapshot() |
||||
ps := h.Percentiles(c.Percentiles) |
||||
fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, h.Count(), now) |
||||
fmt.Fprintf(w, "%s.%s.min %d %d\n", c.Prefix, name, h.Min(), now) |
||||
fmt.Fprintf(w, "%s.%s.max %d %d\n", c.Prefix, name, h.Max(), now) |
||||
fmt.Fprintf(w, "%s.%s.mean %.2f %d\n", c.Prefix, name, h.Mean(), now) |
||||
fmt.Fprintf(w, "%s.%s.std-dev %.2f %d\n", c.Prefix, name, h.StdDev(), now) |
||||
for psIdx, psKey := range c.Percentiles { |
||||
key := strings.Replace(strconv.FormatFloat(psKey*100.0, 'f', -1, 64), ".", "", 1) |
||||
fmt.Fprintf(w, "%s.%s.%s-percentile %.2f %d\n", c.Prefix, name, key, ps[psIdx], now) |
||||
} |
||||
case Meter: |
||||
m := metric.Snapshot() |
||||
fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, m.Count(), now) |
||||
fmt.Fprintf(w, "%s.%s.one-minute %.2f %d\n", c.Prefix, name, m.Rate1(), now) |
||||
fmt.Fprintf(w, "%s.%s.five-minute %.2f %d\n", c.Prefix, name, m.Rate5(), now) |
||||
fmt.Fprintf(w, "%s.%s.fifteen-minute %.2f %d\n", c.Prefix, name, m.Rate15(), now) |
||||
fmt.Fprintf(w, "%s.%s.mean %.2f %d\n", c.Prefix, name, m.RateMean(), now) |
||||
case Timer: |
||||
t := metric.Snapshot() |
||||
ps := t.Percentiles(c.Percentiles) |
||||
fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, t.Count(), now) |
||||
fmt.Fprintf(w, "%s.%s.min %d %d\n", c.Prefix, name, t.Min()/int64(du), now) |
||||
fmt.Fprintf(w, "%s.%s.max %d %d\n", c.Prefix, name, t.Max()/int64(du), now) |
||||
fmt.Fprintf(w, "%s.%s.mean %.2f %d\n", c.Prefix, name, t.Mean()/du, now) |
||||
fmt.Fprintf(w, "%s.%s.std-dev %.2f %d\n", c.Prefix, name, t.StdDev()/du, now) |
||||
for psIdx, psKey := range c.Percentiles { |
||||
key := strings.Replace(strconv.FormatFloat(psKey*100.0, 'f', -1, 64), ".", "", 1) |
||||
fmt.Fprintf(w, "%s.%s.%s-percentile %.2f %d\n", c.Prefix, name, key, ps[psIdx], now) |
||||
} |
||||
fmt.Fprintf(w, "%s.%s.one-minute %.2f %d\n", c.Prefix, name, t.Rate1(), now) |
||||
fmt.Fprintf(w, "%s.%s.five-minute %.2f %d\n", c.Prefix, name, t.Rate5(), now) |
||||
fmt.Fprintf(w, "%s.%s.fifteen-minute %.2f %d\n", c.Prefix, name, t.Rate15(), now) |
||||
fmt.Fprintf(w, "%s.%s.mean-rate %.2f %d\n", c.Prefix, name, t.RateMean(), now) |
||||
} |
||||
w.Flush() |
||||
}) |
||||
return nil |
||||
} |
@ -0,0 +1,22 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"net" |
||||
"time" |
||||
) |
||||
|
||||
func ExampleGraphite() { |
||||
addr, _ := net.ResolveTCPAddr("net", ":2003") |
||||
go Graphite(DefaultRegistry, 1*time.Second, "some.prefix", addr) |
||||
} |
||||
|
||||
func ExampleGraphiteWithConfig() { |
||||
addr, _ := net.ResolveTCPAddr("net", ":2003") |
||||
go GraphiteWithConfig(GraphiteConfig{ |
||||
Addr: addr, |
||||
Registry: DefaultRegistry, |
||||
FlushInterval: 1 * time.Second, |
||||
DurationUnit: time.Millisecond, |
||||
Percentiles: []float64{ 0.5, 0.75, 0.99, 0.999 }, |
||||
}) |
||||
} |
@ -0,0 +1,61 @@ |
||||
package metrics |
||||
|
||||
// Healthchecks hold an error value describing an arbitrary up/down status.
|
||||
type Healthcheck interface { |
||||
Check() |
||||
Error() error |
||||
Healthy() |
||||
Unhealthy(error) |
||||
} |
||||
|
||||
// NewHealthcheck constructs a new Healthcheck which will use the given
|
||||
// function to update its status.
|
||||
func NewHealthcheck(f func(Healthcheck)) Healthcheck { |
||||
if UseNilMetrics { |
||||
return NilHealthcheck{} |
||||
} |
||||
return &StandardHealthcheck{nil, f} |
||||
} |
||||
|
||||
// NilHealthcheck is a no-op.
|
||||
type NilHealthcheck struct{} |
||||
|
||||
// Check is a no-op.
|
||||
func (NilHealthcheck) Check() {} |
||||
|
||||
// Error is a no-op.
|
||||
func (NilHealthcheck) Error() error { return nil } |
||||
|
||||
// Healthy is a no-op.
|
||||
func (NilHealthcheck) Healthy() {} |
||||
|
||||
// Unhealthy is a no-op.
|
||||
func (NilHealthcheck) Unhealthy(error) {} |
||||
|
||||
// StandardHealthcheck is the standard implementation of a Healthcheck and
|
||||
// stores the status and a function to call to update the status.
|
||||
type StandardHealthcheck struct { |
||||
err error |
||||
f func(Healthcheck) |
||||
} |
||||
|
||||
// Check runs the healthcheck function to update the healthcheck's status.
|
||||
func (h *StandardHealthcheck) Check() { |
||||
h.f(h) |
||||
} |
||||
|
||||
// Error returns the healthcheck's status, which will be nil if it is healthy.
|
||||
func (h *StandardHealthcheck) Error() error { |
||||
return h.err |
||||
} |
||||
|
||||
// Healthy marks the healthcheck as healthy.
|
||||
func (h *StandardHealthcheck) Healthy() { |
||||
h.err = nil |
||||
} |
||||
|
||||
// Unhealthy marks the healthcheck as unhealthy. The error is stored and
|
||||
// may be retrieved by the Error method.
|
||||
func (h *StandardHealthcheck) Unhealthy(err error) { |
||||
h.err = err |
||||
} |
@ -0,0 +1,202 @@ |
||||
package metrics |
||||
|
||||
// Histograms calculate distribution statistics from a series of int64 values.
|
||||
type Histogram interface { |
||||
Clear() |
||||
Count() int64 |
||||
Max() int64 |
||||
Mean() float64 |
||||
Min() int64 |
||||
Percentile(float64) float64 |
||||
Percentiles([]float64) []float64 |
||||
Sample() Sample |
||||
Snapshot() Histogram |
||||
StdDev() float64 |
||||
Sum() int64 |
||||
Update(int64) |
||||
Variance() float64 |
||||
} |
||||
|
||||
// GetOrRegisterHistogram returns an existing Histogram or constructs and
|
||||
// registers a new StandardHistogram.
|
||||
func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram { |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
return r.GetOrRegister(name, func() Histogram { return NewHistogram(s) }).(Histogram) |
||||
} |
||||
|
||||
// NewHistogram constructs a new StandardHistogram from a Sample.
|
||||
func NewHistogram(s Sample) Histogram { |
||||
if UseNilMetrics { |
||||
return NilHistogram{} |
||||
} |
||||
return &StandardHistogram{sample: s} |
||||
} |
||||
|
||||
// NewRegisteredHistogram constructs and registers a new StandardHistogram from
|
||||
// a Sample.
|
||||
func NewRegisteredHistogram(name string, r Registry, s Sample) Histogram { |
||||
c := NewHistogram(s) |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
r.Register(name, c) |
||||
return c |
||||
} |
||||
|
||||
// HistogramSnapshot is a read-only copy of another Histogram.
|
||||
type HistogramSnapshot struct { |
||||
sample *SampleSnapshot |
||||
} |
||||
|
||||
// Clear panics.
|
||||
func (*HistogramSnapshot) Clear() { |
||||
panic("Clear called on a HistogramSnapshot") |
||||
} |
||||
|
||||
// Count returns the number of samples recorded at the time the snapshot was
|
||||
// taken.
|
||||
func (h *HistogramSnapshot) Count() int64 { return h.sample.Count() } |
||||
|
||||
// Max returns the maximum value in the sample at the time the snapshot was
|
||||
// taken.
|
||||
func (h *HistogramSnapshot) Max() int64 { return h.sample.Max() } |
||||
|
||||
// Mean returns the mean of the values in the sample at the time the snapshot
|
||||
// was taken.
|
||||
func (h *HistogramSnapshot) Mean() float64 { return h.sample.Mean() } |
||||
|
||||
// Min returns the minimum value in the sample at the time the snapshot was
|
||||
// taken.
|
||||
func (h *HistogramSnapshot) Min() int64 { return h.sample.Min() } |
||||
|
||||
// Percentile returns an arbitrary percentile of values in the sample at the
|
||||
// time the snapshot was taken.
|
||||
func (h *HistogramSnapshot) Percentile(p float64) float64 { |
||||
return h.sample.Percentile(p) |
||||
} |
||||
|
||||
// Percentiles returns a slice of arbitrary percentiles of values in the sample
|
||||
// at the time the snapshot was taken.
|
||||
func (h *HistogramSnapshot) Percentiles(ps []float64) []float64 { |
||||
return h.sample.Percentiles(ps) |
||||
} |
||||
|
||||
// Sample returns the Sample underlying the histogram.
|
||||
func (h *HistogramSnapshot) Sample() Sample { return h.sample } |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (h *HistogramSnapshot) Snapshot() Histogram { return h } |
||||
|
||||
// StdDev returns the standard deviation of the values in the sample at the
|
||||
// time the snapshot was taken.
|
||||
func (h *HistogramSnapshot) StdDev() float64 { return h.sample.StdDev() } |
||||
|
||||
// Sum returns the sum in the sample at the time the snapshot was taken.
|
||||
func (h *HistogramSnapshot) Sum() int64 { return h.sample.Sum() } |
||||
|
||||
// Update panics.
|
||||
func (*HistogramSnapshot) Update(int64) { |
||||
panic("Update called on a HistogramSnapshot") |
||||
} |
||||
|
||||
// Variance returns the variance of inputs at the time the snapshot was taken.
|
||||
func (h *HistogramSnapshot) Variance() float64 { return h.sample.Variance() } |
||||
|
||||
// NilHistogram is a no-op Histogram.
|
||||
type NilHistogram struct{} |
||||
|
||||
// Clear is a no-op.
|
||||
func (NilHistogram) Clear() {} |
||||
|
||||
// Count is a no-op.
|
||||
func (NilHistogram) Count() int64 { return 0 } |
||||
|
||||
// Max is a no-op.
|
||||
func (NilHistogram) Max() int64 { return 0 } |
||||
|
||||
// Mean is a no-op.
|
||||
func (NilHistogram) Mean() float64 { return 0.0 } |
||||
|
||||
// Min is a no-op.
|
||||
func (NilHistogram) Min() int64 { return 0 } |
||||
|
||||
// Percentile is a no-op.
|
||||
func (NilHistogram) Percentile(p float64) float64 { return 0.0 } |
||||
|
||||
// Percentiles is a no-op.
|
||||
func (NilHistogram) Percentiles(ps []float64) []float64 { |
||||
return make([]float64, len(ps)) |
||||
} |
||||
|
||||
// Sample is a no-op.
|
||||
func (NilHistogram) Sample() Sample { return NilSample{} } |
||||
|
||||
// Snapshot is a no-op.
|
||||
func (NilHistogram) Snapshot() Histogram { return NilHistogram{} } |
||||
|
||||
// StdDev is a no-op.
|
||||
func (NilHistogram) StdDev() float64 { return 0.0 } |
||||
|
||||
// Sum is a no-op.
|
||||
func (NilHistogram) Sum() int64 { return 0 } |
||||
|
||||
// Update is a no-op.
|
||||
func (NilHistogram) Update(v int64) {} |
||||
|
||||
// Variance is a no-op.
|
||||
func (NilHistogram) Variance() float64 { return 0.0 } |
||||
|
||||
// StandardHistogram is the standard implementation of a Histogram and uses a
|
||||
// Sample to bound its memory use.
|
||||
type StandardHistogram struct { |
||||
sample Sample |
||||
} |
||||
|
||||
// Clear clears the histogram and its sample.
|
||||
func (h *StandardHistogram) Clear() { h.sample.Clear() } |
||||
|
||||
// Count returns the number of samples recorded since the histogram was last
|
||||
// cleared.
|
||||
func (h *StandardHistogram) Count() int64 { return h.sample.Count() } |
||||
|
||||
// Max returns the maximum value in the sample.
|
||||
func (h *StandardHistogram) Max() int64 { return h.sample.Max() } |
||||
|
||||
// Mean returns the mean of the values in the sample.
|
||||
func (h *StandardHistogram) Mean() float64 { return h.sample.Mean() } |
||||
|
||||
// Min returns the minimum value in the sample.
|
||||
func (h *StandardHistogram) Min() int64 { return h.sample.Min() } |
||||
|
||||
// Percentile returns an arbitrary percentile of the values in the sample.
|
||||
func (h *StandardHistogram) Percentile(p float64) float64 { |
||||
return h.sample.Percentile(p) |
||||
} |
||||
|
||||
// Percentiles returns a slice of arbitrary percentiles of the values in the
|
||||
// sample.
|
||||
func (h *StandardHistogram) Percentiles(ps []float64) []float64 { |
||||
return h.sample.Percentiles(ps) |
||||
} |
||||
|
||||
// Sample returns the Sample underlying the histogram.
|
||||
func (h *StandardHistogram) Sample() Sample { return h.sample } |
||||
|
||||
// Snapshot returns a read-only copy of the histogram.
|
||||
func (h *StandardHistogram) Snapshot() Histogram { |
||||
return &HistogramSnapshot{sample: h.sample.Snapshot().(*SampleSnapshot)} |
||||
} |
||||
|
||||
// StdDev returns the standard deviation of the values in the sample.
|
||||
func (h *StandardHistogram) StdDev() float64 { return h.sample.StdDev() } |
||||
|
||||
// Sum returns the sum in the sample.
|
||||
func (h *StandardHistogram) Sum() int64 { return h.sample.Sum() } |
||||
|
||||
// Update samples a new value.
|
||||
func (h *StandardHistogram) Update(v int64) { h.sample.Update(v) } |
||||
|
||||
// Variance returns the variance of the values in the sample.
|
||||
func (h *StandardHistogram) Variance() float64 { return h.sample.Variance() } |
@ -0,0 +1,95 @@ |
||||
package metrics |
||||
|
||||
import "testing" |
||||
|
||||
func BenchmarkHistogram(b *testing.B) { |
||||
h := NewHistogram(NewUniformSample(100)) |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
h.Update(int64(i)) |
||||
} |
||||
} |
||||
|
||||
func TestGetOrRegisterHistogram(t *testing.T) { |
||||
r := NewRegistry() |
||||
s := NewUniformSample(100) |
||||
NewRegisteredHistogram("foo", r, s).Update(47) |
||||
if h := GetOrRegisterHistogram("foo", r, s); 1 != h.Count() { |
||||
t.Fatal(h) |
||||
} |
||||
} |
||||
|
||||
func TestHistogram10000(t *testing.T) { |
||||
h := NewHistogram(NewUniformSample(100000)) |
||||
for i := 1; i <= 10000; i++ { |
||||
h.Update(int64(i)) |
||||
} |
||||
testHistogram10000(t, h) |
||||
} |
||||
|
||||
func TestHistogramEmpty(t *testing.T) { |
||||
h := NewHistogram(NewUniformSample(100)) |
||||
if count := h.Count(); 0 != count { |
||||
t.Errorf("h.Count(): 0 != %v\n", count) |
||||
} |
||||
if min := h.Min(); 0 != min { |
||||
t.Errorf("h.Min(): 0 != %v\n", min) |
||||
} |
||||
if max := h.Max(); 0 != max { |
||||
t.Errorf("h.Max(): 0 != %v\n", max) |
||||
} |
||||
if mean := h.Mean(); 0.0 != mean { |
||||
t.Errorf("h.Mean(): 0.0 != %v\n", mean) |
||||
} |
||||
if stdDev := h.StdDev(); 0.0 != stdDev { |
||||
t.Errorf("h.StdDev(): 0.0 != %v\n", stdDev) |
||||
} |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.99}) |
||||
if 0.0 != ps[0] { |
||||
t.Errorf("median: 0.0 != %v\n", ps[0]) |
||||
} |
||||
if 0.0 != ps[1] { |
||||
t.Errorf("75th percentile: 0.0 != %v\n", ps[1]) |
||||
} |
||||
if 0.0 != ps[2] { |
||||
t.Errorf("99th percentile: 0.0 != %v\n", ps[2]) |
||||
} |
||||
} |
||||
|
||||
func TestHistogramSnapshot(t *testing.T) { |
||||
h := NewHistogram(NewUniformSample(100000)) |
||||
for i := 1; i <= 10000; i++ { |
||||
h.Update(int64(i)) |
||||
} |
||||
snapshot := h.Snapshot() |
||||
h.Update(0) |
||||
testHistogram10000(t, snapshot) |
||||
} |
||||
|
||||
func testHistogram10000(t *testing.T, h Histogram) { |
||||
if count := h.Count(); 10000 != count { |
||||
t.Errorf("h.Count(): 10000 != %v\n", count) |
||||
} |
||||
if min := h.Min(); 1 != min { |
||||
t.Errorf("h.Min(): 1 != %v\n", min) |
||||
} |
||||
if max := h.Max(); 10000 != max { |
||||
t.Errorf("h.Max(): 10000 != %v\n", max) |
||||
} |
||||
if mean := h.Mean(); 5000.5 != mean { |
||||
t.Errorf("h.Mean(): 5000.5 != %v\n", mean) |
||||
} |
||||
if stdDev := h.StdDev(); 2886.751331514372 != stdDev { |
||||
t.Errorf("h.StdDev(): 2886.751331514372 != %v\n", stdDev) |
||||
} |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.99}) |
||||
if 5000.5 != ps[0] { |
||||
t.Errorf("median: 5000.5 != %v\n", ps[0]) |
||||
} |
||||
if 7500.75 != ps[1] { |
||||
t.Errorf("75th percentile: 7500.75 != %v\n", ps[1]) |
||||
} |
||||
if 9900.99 != ps[2] { |
||||
t.Errorf("99th percentile: 9900.99 != %v\n", ps[2]) |
||||
} |
||||
} |
@ -0,0 +1,114 @@ |
||||
package influxdb |
||||
|
||||
import ( |
||||
"fmt" |
||||
influxClient "github.com/influxdb/influxdb/client" |
||||
"github.com/rcrowley/go-metrics" |
||||
"log" |
||||
"time" |
||||
) |
||||
|
||||
type Config struct { |
||||
Host string |
||||
Database string |
||||
Username string |
||||
Password string |
||||
} |
||||
|
||||
func Influxdb(r metrics.Registry, d time.Duration, config *Config) { |
||||
client, err := influxClient.NewClient(&influxClient.ClientConfig{ |
||||
Host: config.Host, |
||||
Database: config.Database, |
||||
Username: config.Username, |
||||
Password: config.Password, |
||||
}) |
||||
if err != nil { |
||||
log.Println(err) |
||||
return |
||||
} |
||||
|
||||
for _ = range time.Tick(d) { |
||||
if err := send(r, client); err != nil { |
||||
log.Println(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func send(r metrics.Registry, client *influxClient.Client) error { |
||||
series := []*influxClient.Series{} |
||||
|
||||
r.Each(func(name string, i interface{}) { |
||||
now := getCurrentTime() |
||||
switch metric := i.(type) { |
||||
case metrics.Counter: |
||||
series = append(series, &influxClient.Series{ |
||||
Name: fmt.Sprintf("%s.count", name), |
||||
Columns: []string{"time", "count"}, |
||||
Points: [][]interface{}{ |
||||
{now, metric.Count()}, |
||||
}, |
||||
}) |
||||
case metrics.Gauge: |
||||
series = append(series, &influxClient.Series{ |
||||
Name: fmt.Sprintf("%s.value", name), |
||||
Columns: []string{"time", "value"}, |
||||
Points: [][]interface{}{ |
||||
{now, metric.Value()}, |
||||
}, |
||||
}) |
||||
case metrics.GaugeFloat64: |
||||
series = append(series, &influxClient.Series{ |
||||
Name: fmt.Sprintf("%s.value", name), |
||||
Columns: []string{"time", "value"}, |
||||
Points: [][]interface{}{ |
||||
{now, metric.Value()}, |
||||
}, |
||||
}) |
||||
case metrics.Histogram: |
||||
h := metric.Snapshot() |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
series = append(series, &influxClient.Series{ |
||||
Name: fmt.Sprintf("%s.histogram", name), |
||||
Columns: []string{"time", "count", "min", "max", "mean", "std-dev", |
||||
"50-percentile", "75-percentile", "95-percentile", |
||||
"99-percentile", "999-percentile"}, |
||||
Points: [][]interface{}{ |
||||
{now, h.Count(), h.Min(), h.Max(), h.Mean(), h.StdDev(), |
||||
ps[0], ps[1], ps[2], ps[3], ps[4]}, |
||||
}, |
||||
}) |
||||
case metrics.Meter: |
||||
m := metric.Snapshot() |
||||
series = append(series, &influxClient.Series{ |
||||
Name: fmt.Sprintf("%s.meter", name), |
||||
Columns: []string{"count", "one-minute", |
||||
"five-minute", "fifteen-minute", "mean"}, |
||||
Points: [][]interface{}{ |
||||
{m.Count(), m.Rate1(), m.Rate5(), m.Rate15(), m.RateMean()}, |
||||
}, |
||||
}) |
||||
case metrics.Timer: |
||||
h := metric.Snapshot() |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
series = append(series, &influxClient.Series{ |
||||
Name: fmt.Sprintf("%s.timer", name), |
||||
Columns: []string{"count", "min", "max", "mean", "std-dev", |
||||
"50-percentile", "75-percentile", "95-percentile", |
||||
"99-percentile", "999-percentile", "one-minute", "five-minute", "fifteen-minute", "mean-rate"}, |
||||
Points: [][]interface{}{ |
||||
{h.Count(), h.Min(), h.Max(), h.Mean(), h.StdDev(), |
||||
ps[0], ps[1], ps[2], ps[3], ps[4], |
||||
h.Rate1(), h.Rate5(), h.Rate15(), h.RateMean()}, |
||||
}, |
||||
}) |
||||
} |
||||
}) |
||||
if err := client.WriteSeries(series); err != nil { |
||||
log.Println(err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func getCurrentTime() int64 { |
||||
return time.Now().UnixNano() / 1000000 |
||||
} |
@ -0,0 +1,83 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"io" |
||||
"time" |
||||
) |
||||
|
||||
// MarshalJSON returns a byte slice containing a JSON representation of all
|
||||
// the metrics in the Registry.
|
||||
func (r StandardRegistry) MarshalJSON() ([]byte, error) { |
||||
data := make(map[string]map[string]interface{}) |
||||
r.Each(func(name string, i interface{}) { |
||||
values := make(map[string]interface{}) |
||||
switch metric := i.(type) { |
||||
case Counter: |
||||
values["count"] = metric.Count() |
||||
case Gauge: |
||||
values["value"] = metric.Value() |
||||
case GaugeFloat64: |
||||
values["value"] = metric.Value() |
||||
case Healthcheck: |
||||
values["error"] = nil |
||||
metric.Check() |
||||
if err := metric.Error(); nil != err { |
||||
values["error"] = metric.Error().Error() |
||||
} |
||||
case Histogram: |
||||
h := metric.Snapshot() |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
values["count"] = h.Count() |
||||
values["min"] = h.Min() |
||||
values["max"] = h.Max() |
||||
values["mean"] = h.Mean() |
||||
values["stddev"] = h.StdDev() |
||||
values["median"] = ps[0] |
||||
values["75%"] = ps[1] |
||||
values["95%"] = ps[2] |
||||
values["99%"] = ps[3] |
||||
values["99.9%"] = ps[4] |
||||
case Meter: |
||||
m := metric.Snapshot() |
||||
values["count"] = m.Count() |
||||
values["1m.rate"] = m.Rate1() |
||||
values["5m.rate"] = m.Rate5() |
||||
values["15m.rate"] = m.Rate15() |
||||
values["mean.rate"] = m.RateMean() |
||||
case Timer: |
||||
t := metric.Snapshot() |
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
values["count"] = t.Count() |
||||
values["min"] = t.Min() |
||||
values["max"] = t.Max() |
||||
values["mean"] = t.Mean() |
||||
values["stddev"] = t.StdDev() |
||||
values["median"] = ps[0] |
||||
values["75%"] = ps[1] |
||||
values["95%"] = ps[2] |
||||
values["99%"] = ps[3] |
||||
values["99.9%"] = ps[4] |
||||
values["1m.rate"] = t.Rate1() |
||||
values["5m.rate"] = t.Rate5() |
||||
values["15m.rate"] = t.Rate15() |
||||
values["mean.rate"] = t.RateMean() |
||||
} |
||||
data[name] = values |
||||
}) |
||||
return json.Marshal(data) |
||||
} |
||||
|
||||
// WriteJSON writes metrics from the given registry periodically to the
|
||||
// specified io.Writer as JSON.
|
||||
func WriteJSON(r Registry, d time.Duration, w io.Writer) { |
||||
for _ = range time.Tick(d) { |
||||
WriteJSONOnce(r, w) |
||||
} |
||||
} |
||||
|
||||
// WriteJSONOnce writes metrics from the given registry to the specified
|
||||
// io.Writer as JSON.
|
||||
func WriteJSONOnce(r Registry, w io.Writer) { |
||||
json.NewEncoder(w).Encode(r) |
||||
} |
@ -0,0 +1,28 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"testing" |
||||
) |
||||
|
||||
func TestRegistryMarshallJSON(t *testing.T) { |
||||
b := &bytes.Buffer{} |
||||
enc := json.NewEncoder(b) |
||||
r := NewRegistry() |
||||
r.Register("counter", NewCounter()) |
||||
enc.Encode(r) |
||||
if s := b.String(); "{\"counter\":{\"count\":0}}\n" != s { |
||||
t.Fatalf(s) |
||||
} |
||||
} |
||||
|
||||
func TestRegistryWriteJSONOnce(t *testing.T) { |
||||
r := NewRegistry() |
||||
r.Register("counter", NewCounter()) |
||||
b := &bytes.Buffer{} |
||||
WriteJSONOnce(r, b) |
||||
if s := b.String(); s != "{\"counter\":{\"count\":0}}\n" { |
||||
t.Fail() |
||||
} |
||||
} |
@ -0,0 +1,102 @@ |
||||
package librato |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net/http" |
||||
) |
||||
|
||||
const Operations = "operations" |
||||
const OperationsShort = "ops" |
||||
|
||||
type LibratoClient struct { |
||||
Email, Token string |
||||
} |
||||
|
||||
// property strings
|
||||
const ( |
||||
// display attributes
|
||||
Color = "color" |
||||
DisplayMax = "display_max" |
||||
DisplayMin = "display_min" |
||||
DisplayUnitsLong = "display_units_long" |
||||
DisplayUnitsShort = "display_units_short" |
||||
DisplayStacked = "display_stacked" |
||||
DisplayTransform = "display_transform" |
||||
// special gauge display attributes
|
||||
SummarizeFunction = "summarize_function" |
||||
Aggregate = "aggregate" |
||||
|
||||
// metric keys
|
||||
Name = "name" |
||||
Period = "period" |
||||
Description = "description" |
||||
DisplayName = "display_name" |
||||
Attributes = "attributes" |
||||
|
||||
// measurement keys
|
||||
MeasureTime = "measure_time" |
||||
Source = "source" |
||||
Value = "value" |
||||
|
||||
// special gauge keys
|
||||
Count = "count" |
||||
Sum = "sum" |
||||
Max = "max" |
||||
Min = "min" |
||||
SumSquares = "sum_squares" |
||||
|
||||
// batch keys
|
||||
Counters = "counters" |
||||
Gauges = "gauges" |
||||
|
||||
MetricsPostUrl = "https://metrics-api.librato.com/v1/metrics" |
||||
) |
||||
|
||||
type Measurement map[string]interface{} |
||||
type Metric map[string]interface{} |
||||
|
||||
type Batch struct { |
||||
Gauges []Measurement `json:"gauges,omitempty"` |
||||
Counters []Measurement `json:"counters,omitempty"` |
||||
MeasureTime int64 `json:"measure_time"` |
||||
Source string `json:"source"` |
||||
} |
||||
|
||||
func (self *LibratoClient) PostMetrics(batch Batch) (err error) { |
||||
var ( |
||||
js []byte |
||||
req *http.Request |
||||
resp *http.Response |
||||
) |
||||
|
||||
if len(batch.Counters) == 0 && len(batch.Gauges) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
if js, err = json.Marshal(batch); err != nil { |
||||
return |
||||
} |
||||
|
||||
if req, err = http.NewRequest("POST", MetricsPostUrl, bytes.NewBuffer(js)); err != nil { |
||||
return |
||||
} |
||||
|
||||
req.Header.Set("Content-Type", "application/json") |
||||
req.SetBasicAuth(self.Email, self.Token) |
||||
|
||||
if resp, err = http.DefaultClient.Do(req); err != nil { |
||||
return |
||||
} |
||||
|
||||
if resp.StatusCode != http.StatusOK { |
||||
var body []byte |
||||
if body, err = ioutil.ReadAll(resp.Body); err != nil { |
||||
body = []byte(fmt.Sprintf("(could not fetch response body for error: %s)", err)) |
||||
} |
||||
err = fmt.Errorf("Unable to post to Librato: %d %s %s", resp.StatusCode, resp.Status, string(body)) |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,230 @@ |
||||
package librato |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"math" |
||||
"regexp" |
||||
"time" |
||||
|
||||
"github.com/rcrowley/go-metrics" |
||||
) |
||||
|
||||
// a regexp for extracting the unit from time.Duration.String
|
||||
var unitRegexp = regexp.MustCompile("[^\\d]+$") |
||||
|
||||
// a helper that turns a time.Duration into librato display attributes for timer metrics
|
||||
func translateTimerAttributes(d time.Duration) (attrs map[string]interface{}) { |
||||
attrs = make(map[string]interface{}) |
||||
attrs[DisplayTransform] = fmt.Sprintf("x/%d", int64(d)) |
||||
attrs[DisplayUnitsShort] = string(unitRegexp.Find([]byte(d.String()))) |
||||
return |
||||
} |
||||
|
||||
type Reporter struct { |
||||
Email, Token string |
||||
Source string |
||||
Interval time.Duration |
||||
Registry metrics.Registry |
||||
Percentiles []float64 // percentiles to report on histogram metrics
|
||||
TimerAttributes map[string]interface{} // units in which timers will be displayed
|
||||
intervalSec int64 |
||||
} |
||||
|
||||
func NewReporter(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) *Reporter { |
||||
return &Reporter{e, t, s, d, r, p, translateTimerAttributes(u), int64(d / time.Second)} |
||||
} |
||||
|
||||
func Librato(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) { |
||||
NewReporter(r, d, e, t, s, p, u).Run() |
||||
} |
||||
|
||||
func (self *Reporter) Run() { |
||||
ticker := time.Tick(self.Interval) |
||||
metricsApi := &LibratoClient{self.Email, self.Token} |
||||
for now := range ticker { |
||||
var metrics Batch |
||||
var err error |
||||
if metrics, err = self.BuildRequest(now, self.Registry); err != nil { |
||||
log.Printf("ERROR constructing librato request body %s", err) |
||||
continue |
||||
} |
||||
if err := metricsApi.PostMetrics(metrics); err != nil { |
||||
log.Printf("ERROR sending metrics to librato %s", err) |
||||
continue |
||||
} |
||||
} |
||||
} |
||||
|
||||
// calculate sum of squares from data provided by metrics.Histogram
|
||||
// see http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
|
||||
func sumSquares(s metrics.Sample) float64 { |
||||
count := float64(s.Count()) |
||||
sumSquared := math.Pow(count*s.Mean(), 2) |
||||
sumSquares := math.Pow(count*s.StdDev(), 2) + sumSquared/count |
||||
if math.IsNaN(sumSquares) { |
||||
return 0.0 |
||||
} |
||||
return sumSquares |
||||
} |
||||
func sumSquaresTimer(t metrics.Timer) float64 { |
||||
count := float64(t.Count()) |
||||
sumSquared := math.Pow(count*t.Mean(), 2) |
||||
sumSquares := math.Pow(count*t.StdDev(), 2) + sumSquared/count |
||||
if math.IsNaN(sumSquares) { |
||||
return 0.0 |
||||
} |
||||
return sumSquares |
||||
} |
||||
|
||||
func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Batch, err error) { |
||||
snapshot = Batch{ |
||||
// coerce timestamps to a stepping fn so that they line up in Librato graphs
|
||||
MeasureTime: (now.Unix() / self.intervalSec) * self.intervalSec, |
||||
Source: self.Source, |
||||
} |
||||
snapshot.Gauges = make([]Measurement, 0) |
||||
snapshot.Counters = make([]Measurement, 0) |
||||
histogramGaugeCount := 1 + len(self.Percentiles) |
||||
r.Each(func(name string, metric interface{}) { |
||||
measurement := Measurement{} |
||||
measurement[Period] = self.Interval.Seconds() |
||||
switch m := metric.(type) { |
||||
case metrics.Counter: |
||||
if m.Count() > 0 { |
||||
measurement[Name] = fmt.Sprintf("%s.%s", name, "count") |
||||
measurement[Value] = float64(m.Count()) |
||||
measurement[Attributes] = map[string]interface{}{ |
||||
DisplayUnitsLong: Operations, |
||||
DisplayUnitsShort: OperationsShort, |
||||
DisplayMin: "0", |
||||
} |
||||
snapshot.Counters = append(snapshot.Counters, measurement) |
||||
} |
||||
case metrics.Gauge: |
||||
measurement[Name] = name |
||||
measurement[Value] = float64(m.Value()) |
||||
snapshot.Gauges = append(snapshot.Gauges, measurement) |
||||
case metrics.GaugeFloat64: |
||||
measurement[Name] = name |
||||
measurement[Value] = float64(m.Value()) |
||||
snapshot.Gauges = append(snapshot.Gauges, measurement) |
||||
case metrics.Histogram: |
||||
if m.Count() > 0 { |
||||
gauges := make([]Measurement, histogramGaugeCount, histogramGaugeCount) |
||||
s := m.Sample() |
||||
measurement[Name] = fmt.Sprintf("%s.%s", name, "hist") |
||||
measurement[Count] = uint64(s.Count()) |
||||
measurement[Max] = float64(s.Max()) |
||||
measurement[Min] = float64(s.Min()) |
||||
measurement[Sum] = float64(s.Sum()) |
||||
measurement[SumSquares] = sumSquares(s) |
||||
gauges[0] = measurement |
||||
for i, p := range self.Percentiles { |
||||
gauges[i+1] = Measurement{ |
||||
Name: fmt.Sprintf("%s.%.2f", measurement[Name], p), |
||||
Value: s.Percentile(p), |
||||
Period: measurement[Period], |
||||
} |
||||
} |
||||
snapshot.Gauges = append(snapshot.Gauges, gauges...) |
||||
} |
||||
case metrics.Meter: |
||||
measurement[Name] = name |
||||
measurement[Value] = float64(m.Count()) |
||||
snapshot.Counters = append(snapshot.Counters, measurement) |
||||
snapshot.Gauges = append(snapshot.Gauges, |
||||
Measurement{ |
||||
Name: fmt.Sprintf("%s.%s", name, "1min"), |
||||
Value: m.Rate1(), |
||||
Period: int64(self.Interval.Seconds()), |
||||
Attributes: map[string]interface{}{ |
||||
DisplayUnitsLong: Operations, |
||||
DisplayUnitsShort: OperationsShort, |
||||
DisplayMin: "0", |
||||
}, |
||||
}, |
||||
Measurement{ |
||||
Name: fmt.Sprintf("%s.%s", name, "5min"), |
||||
Value: m.Rate5(), |
||||
Period: int64(self.Interval.Seconds()), |
||||
Attributes: map[string]interface{}{ |
||||
DisplayUnitsLong: Operations, |
||||
DisplayUnitsShort: OperationsShort, |
||||
DisplayMin: "0", |
||||
}, |
||||
}, |
||||
Measurement{ |
||||
Name: fmt.Sprintf("%s.%s", name, "15min"), |
||||
Value: m.Rate15(), |
||||
Period: int64(self.Interval.Seconds()), |
||||
Attributes: map[string]interface{}{ |
||||
DisplayUnitsLong: Operations, |
||||
DisplayUnitsShort: OperationsShort, |
||||
DisplayMin: "0", |
||||
}, |
||||
}, |
||||
) |
||||
case metrics.Timer: |
||||
measurement[Name] = name |
||||
measurement[Value] = float64(m.Count()) |
||||
snapshot.Counters = append(snapshot.Counters, measurement) |
||||
if m.Count() > 0 { |
||||
libratoName := fmt.Sprintf("%s.%s", name, "timer.mean") |
||||
gauges := make([]Measurement, histogramGaugeCount, histogramGaugeCount) |
||||
gauges[0] = Measurement{ |
||||
Name: libratoName, |
||||
Count: uint64(m.Count()), |
||||
Sum: m.Mean() * float64(m.Count()), |
||||
Max: float64(m.Max()), |
||||
Min: float64(m.Min()), |
||||
SumSquares: sumSquaresTimer(m), |
||||
Period: int64(self.Interval.Seconds()), |
||||
Attributes: self.TimerAttributes, |
||||
} |
||||
for i, p := range self.Percentiles { |
||||
gauges[i+1] = Measurement{ |
||||
Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100), |
||||
Value: m.Percentile(p), |
||||
Period: int64(self.Interval.Seconds()), |
||||
Attributes: self.TimerAttributes, |
||||
} |
||||
} |
||||
snapshot.Gauges = append(snapshot.Gauges, gauges...) |
||||
snapshot.Gauges = append(snapshot.Gauges, |
||||
Measurement{ |
||||
Name: fmt.Sprintf("%s.%s", name, "rate.1min"), |
||||
Value: m.Rate1(), |
||||
Period: int64(self.Interval.Seconds()), |
||||
Attributes: map[string]interface{}{ |
||||
DisplayUnitsLong: Operations, |
||||
DisplayUnitsShort: OperationsShort, |
||||
DisplayMin: "0", |
||||
}, |
||||
}, |
||||
Measurement{ |
||||
Name: fmt.Sprintf("%s.%s", name, "rate.5min"), |
||||
Value: m.Rate5(), |
||||
Period: int64(self.Interval.Seconds()), |
||||
Attributes: map[string]interface{}{ |
||||
DisplayUnitsLong: Operations, |
||||
DisplayUnitsShort: OperationsShort, |
||||
DisplayMin: "0", |
||||
}, |
||||
}, |
||||
Measurement{ |
||||
Name: fmt.Sprintf("%s.%s", name, "rate.15min"), |
||||
Value: m.Rate15(), |
||||
Period: int64(self.Interval.Seconds()), |
||||
Attributes: map[string]interface{}{ |
||||
DisplayUnitsLong: Operations, |
||||
DisplayUnitsShort: OperationsShort, |
||||
DisplayMin: "0", |
||||
}, |
||||
}, |
||||
) |
||||
} |
||||
} |
||||
}) |
||||
return |
||||
} |
@ -0,0 +1,70 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"log" |
||||
"time" |
||||
) |
||||
|
||||
// Output each metric in the given registry periodically using the given
|
||||
// logger.
|
||||
func Log(r Registry, d time.Duration, l *log.Logger) { |
||||
for _ = range time.Tick(d) { |
||||
r.Each(func(name string, i interface{}) { |
||||
switch metric := i.(type) { |
||||
case Counter: |
||||
l.Printf("counter %s\n", name) |
||||
l.Printf(" count: %9d\n", metric.Count()) |
||||
case Gauge: |
||||
l.Printf("gauge %s\n", name) |
||||
l.Printf(" value: %9d\n", metric.Value()) |
||||
case GaugeFloat64: |
||||
l.Printf("gauge %s\n", name) |
||||
l.Printf(" value: %f\n", metric.Value()) |
||||
case Healthcheck: |
||||
metric.Check() |
||||
l.Printf("healthcheck %s\n", name) |
||||
l.Printf(" error: %v\n", metric.Error()) |
||||
case Histogram: |
||||
h := metric.Snapshot() |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
l.Printf("histogram %s\n", name) |
||||
l.Printf(" count: %9d\n", h.Count()) |
||||
l.Printf(" min: %9d\n", h.Min()) |
||||
l.Printf(" max: %9d\n", h.Max()) |
||||
l.Printf(" mean: %12.2f\n", h.Mean()) |
||||
l.Printf(" stddev: %12.2f\n", h.StdDev()) |
||||
l.Printf(" median: %12.2f\n", ps[0]) |
||||
l.Printf(" 75%%: %12.2f\n", ps[1]) |
||||
l.Printf(" 95%%: %12.2f\n", ps[2]) |
||||
l.Printf(" 99%%: %12.2f\n", ps[3]) |
||||
l.Printf(" 99.9%%: %12.2f\n", ps[4]) |
||||
case Meter: |
||||
m := metric.Snapshot() |
||||
l.Printf("meter %s\n", name) |
||||
l.Printf(" count: %9d\n", m.Count()) |
||||
l.Printf(" 1-min rate: %12.2f\n", m.Rate1()) |
||||
l.Printf(" 5-min rate: %12.2f\n", m.Rate5()) |
||||
l.Printf(" 15-min rate: %12.2f\n", m.Rate15()) |
||||
l.Printf(" mean rate: %12.2f\n", m.RateMean()) |
||||
case Timer: |
||||
t := metric.Snapshot() |
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
l.Printf("timer %s\n", name) |
||||
l.Printf(" count: %9d\n", t.Count()) |
||||
l.Printf(" min: %9d\n", t.Min()) |
||||
l.Printf(" max: %9d\n", t.Max()) |
||||
l.Printf(" mean: %12.2f\n", t.Mean()) |
||||
l.Printf(" stddev: %12.2f\n", t.StdDev()) |
||||
l.Printf(" median: %12.2f\n", ps[0]) |
||||
l.Printf(" 75%%: %12.2f\n", ps[1]) |
||||
l.Printf(" 95%%: %12.2f\n", ps[2]) |
||||
l.Printf(" 99%%: %12.2f\n", ps[3]) |
||||
l.Printf(" 99.9%%: %12.2f\n", ps[4]) |
||||
l.Printf(" 1-min rate: %12.2f\n", t.Rate1()) |
||||
l.Printf(" 5-min rate: %12.2f\n", t.Rate5()) |
||||
l.Printf(" 15-min rate: %12.2f\n", t.Rate15()) |
||||
l.Printf(" mean rate: %12.2f\n", t.RateMean()) |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,285 @@ |
||||
Memory usage |
||||
============ |
||||
|
||||
(Highly unscientific.) |
||||
|
||||
Command used to gather static memory usage: |
||||
|
||||
```sh |
||||
grep ^Vm "/proc/$(ps fax | grep [m]etrics-bench | awk '{print $1}')/status" |
||||
``` |
||||
|
||||
Program used to gather baseline memory usage: |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import "time" |
||||
|
||||
func main() { |
||||
time.Sleep(600e9) |
||||
} |
||||
``` |
||||
|
||||
Baseline |
||||
-------- |
||||
|
||||
``` |
||||
VmPeak: 42604 kB |
||||
VmSize: 42604 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 1120 kB |
||||
VmRSS: 1120 kB |
||||
VmData: 35460 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1020 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 36 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
Program used to gather metric memory usage (with other metrics being similar): |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"metrics" |
||||
"time" |
||||
) |
||||
|
||||
func main() { |
||||
fmt.Sprintf("foo") |
||||
metrics.NewRegistry() |
||||
time.Sleep(600e9) |
||||
} |
||||
``` |
||||
|
||||
1000 counters registered |
||||
------------------------ |
||||
|
||||
``` |
||||
VmPeak: 44016 kB |
||||
VmSize: 44016 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 1928 kB |
||||
VmRSS: 1928 kB |
||||
VmData: 36868 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1024 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 40 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**1.412 kB virtual, TODO 0.808 kB resident per counter.** |
||||
|
||||
100000 counters registered |
||||
-------------------------- |
||||
|
||||
``` |
||||
VmPeak: 55024 kB |
||||
VmSize: 55024 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 12440 kB |
||||
VmRSS: 12440 kB |
||||
VmData: 47876 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1024 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 64 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**0.1242 kB virtual, 0.1132 kB resident per counter.** |
||||
|
||||
1000 gauges registered |
||||
---------------------- |
||||
|
||||
``` |
||||
VmPeak: 44012 kB |
||||
VmSize: 44012 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 1928 kB |
||||
VmRSS: 1928 kB |
||||
VmData: 36868 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1020 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 40 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**1.408 kB virtual, 0.808 kB resident per counter.** |
||||
|
||||
100000 gauges registered |
||||
------------------------ |
||||
|
||||
``` |
||||
VmPeak: 55020 kB |
||||
VmSize: 55020 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 12432 kB |
||||
VmRSS: 12432 kB |
||||
VmData: 47876 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1020 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 60 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**0.12416 kB virtual, 0.11312 resident per gauge.** |
||||
|
||||
1000 histograms with a uniform sample size of 1028 |
||||
-------------------------------------------------- |
||||
|
||||
``` |
||||
VmPeak: 72272 kB |
||||
VmSize: 72272 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 16204 kB |
||||
VmRSS: 16204 kB |
||||
VmData: 65100 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1048 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 80 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**29.668 kB virtual, TODO 15.084 resident per histogram.** |
||||
|
||||
10000 histograms with a uniform sample size of 1028 |
||||
--------------------------------------------------- |
||||
|
||||
``` |
||||
VmPeak: 256912 kB |
||||
VmSize: 256912 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 146204 kB |
||||
VmRSS: 146204 kB |
||||
VmData: 249740 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1048 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 448 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**21.4308 kB virtual, 14.5084 kB resident per histogram.** |
||||
|
||||
50000 histograms with a uniform sample size of 1028 |
||||
--------------------------------------------------- |
||||
|
||||
``` |
||||
VmPeak: 908112 kB |
||||
VmSize: 908112 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 645832 kB |
||||
VmRSS: 645588 kB |
||||
VmData: 900940 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1048 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 1716 kB |
||||
VmSwap: 1544 kB |
||||
``` |
||||
|
||||
**17.31016 kB virtual, 12.88936 kB resident per histogram.** |
||||
|
||||
1000 histograms with an exponentially-decaying sample size of 1028 and alpha of 0.015 |
||||
------------------------------------------------------------------------------------- |
||||
|
||||
``` |
||||
VmPeak: 62480 kB |
||||
VmSize: 62480 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 11572 kB |
||||
VmRSS: 11572 kB |
||||
VmData: 55308 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1048 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 64 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**19.876 kB virtual, 10.452 kB resident per histogram.** |
||||
|
||||
10000 histograms with an exponentially-decaying sample size of 1028 and alpha of 0.015 |
||||
-------------------------------------------------------------------------------------- |
||||
|
||||
``` |
||||
VmPeak: 153296 kB |
||||
VmSize: 153296 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 101176 kB |
||||
VmRSS: 101176 kB |
||||
VmData: 146124 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1048 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 240 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**11.0692 kB virtual, 10.0056 kB resident per histogram.** |
||||
|
||||
50000 histograms with an exponentially-decaying sample size of 1028 and alpha of 0.015 |
||||
-------------------------------------------------------------------------------------- |
||||
|
||||
``` |
||||
VmPeak: 557264 kB |
||||
VmSize: 557264 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 501056 kB |
||||
VmRSS: 501056 kB |
||||
VmData: 550092 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1048 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 1032 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**10.2932 kB virtual, 9.99872 kB resident per histogram.** |
||||
|
||||
1000 meters |
||||
----------- |
||||
|
||||
``` |
||||
VmPeak: 74504 kB |
||||
VmSize: 74504 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 24124 kB |
||||
VmRSS: 24124 kB |
||||
VmData: 67340 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1040 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 92 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**31.9 kB virtual, 23.004 kB resident per meter.** |
||||
|
||||
10000 meters |
||||
------------ |
||||
|
||||
``` |
||||
VmPeak: 278920 kB |
||||
VmSize: 278920 kB |
||||
VmLck: 0 kB |
||||
VmHWM: 227300 kB |
||||
VmRSS: 227300 kB |
||||
VmData: 271756 kB |
||||
VmStk: 136 kB |
||||
VmExe: 1040 kB |
||||
VmLib: 1848 kB |
||||
VmPTE: 488 kB |
||||
VmSwap: 0 kB |
||||
``` |
||||
|
||||
**23.6316 kB virtual, 22.618 kB resident per meter.** |
@ -0,0 +1,233 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// Meters count events to produce exponentially-weighted moving average rates
|
||||
// at one-, five-, and fifteen-minutes and a mean rate.
|
||||
type Meter interface { |
||||
Count() int64 |
||||
Mark(int64) |
||||
Rate1() float64 |
||||
Rate5() float64 |
||||
Rate15() float64 |
||||
RateMean() float64 |
||||
Snapshot() Meter |
||||
} |
||||
|
||||
// GetOrRegisterMeter returns an existing Meter or constructs and registers a
|
||||
// new StandardMeter.
|
||||
func GetOrRegisterMeter(name string, r Registry) Meter { |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
return r.GetOrRegister(name, NewMeter).(Meter) |
||||
} |
||||
|
||||
// NewMeter constructs a new StandardMeter and launches a goroutine.
|
||||
func NewMeter() Meter { |
||||
if UseNilMetrics { |
||||
return NilMeter{} |
||||
} |
||||
m := newStandardMeter() |
||||
arbiter.Lock() |
||||
defer arbiter.Unlock() |
||||
arbiter.meters = append(arbiter.meters, m) |
||||
if !arbiter.started { |
||||
arbiter.started = true |
||||
go arbiter.tick() |
||||
} |
||||
return m |
||||
} |
||||
|
||||
// NewMeter constructs and registers a new StandardMeter and launches a
|
||||
// goroutine.
|
||||
func NewRegisteredMeter(name string, r Registry) Meter { |
||||
c := NewMeter() |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
r.Register(name, c) |
||||
return c |
||||
} |
||||
|
||||
// MeterSnapshot is a read-only copy of another Meter.
|
||||
type MeterSnapshot struct { |
||||
count int64 |
||||
rate1, rate5, rate15, rateMean float64 |
||||
} |
||||
|
||||
// Count returns the count of events at the time the snapshot was taken.
|
||||
func (m *MeterSnapshot) Count() int64 { return m.count } |
||||
|
||||
// Mark panics.
|
||||
func (*MeterSnapshot) Mark(n int64) { |
||||
panic("Mark called on a MeterSnapshot") |
||||
} |
||||
|
||||
// Rate1 returns the one-minute moving average rate of events per second at the
|
||||
// time the snapshot was taken.
|
||||
func (m *MeterSnapshot) Rate1() float64 { return m.rate1 } |
||||
|
||||
// Rate5 returns the five-minute moving average rate of events per second at
|
||||
// the time the snapshot was taken.
|
||||
func (m *MeterSnapshot) Rate5() float64 { return m.rate5 } |
||||
|
||||
// Rate15 returns the fifteen-minute moving average rate of events per second
|
||||
// at the time the snapshot was taken.
|
||||
func (m *MeterSnapshot) Rate15() float64 { return m.rate15 } |
||||
|
||||
// RateMean returns the meter's mean rate of events per second at the time the
|
||||
// snapshot was taken.
|
||||
func (m *MeterSnapshot) RateMean() float64 { return m.rateMean } |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (m *MeterSnapshot) Snapshot() Meter { return m } |
||||
|
||||
// NilMeter is a no-op Meter.
|
||||
type NilMeter struct{} |
||||
|
||||
// Count is a no-op.
|
||||
func (NilMeter) Count() int64 { return 0 } |
||||
|
||||
// Mark is a no-op.
|
||||
func (NilMeter) Mark(n int64) {} |
||||
|
||||
// Rate1 is a no-op.
|
||||
func (NilMeter) Rate1() float64 { return 0.0 } |
||||
|
||||
// Rate5 is a no-op.
|
||||
func (NilMeter) Rate5() float64 { return 0.0 } |
||||
|
||||
// Rate15is a no-op.
|
||||
func (NilMeter) Rate15() float64 { return 0.0 } |
||||
|
||||
// RateMean is a no-op.
|
||||
func (NilMeter) RateMean() float64 { return 0.0 } |
||||
|
||||
// Snapshot is a no-op.
|
||||
func (NilMeter) Snapshot() Meter { return NilMeter{} } |
||||
|
||||
// StandardMeter is the standard implementation of a Meter.
|
||||
type StandardMeter struct { |
||||
lock sync.RWMutex |
||||
snapshot *MeterSnapshot |
||||
a1, a5, a15 EWMA |
||||
startTime time.Time |
||||
} |
||||
|
||||
func newStandardMeter() *StandardMeter { |
||||
return &StandardMeter{ |
||||
snapshot: &MeterSnapshot{}, |
||||
a1: NewEWMA1(), |
||||
a5: NewEWMA5(), |
||||
a15: NewEWMA15(), |
||||
startTime: time.Now(), |
||||
} |
||||
} |
||||
|
||||
// Count returns the number of events recorded.
|
||||
func (m *StandardMeter) Count() int64 { |
||||
m.lock.RLock() |
||||
count := m.snapshot.count |
||||
m.lock.RUnlock() |
||||
return count |
||||
} |
||||
|
||||
// Mark records the occurance of n events.
|
||||
func (m *StandardMeter) Mark(n int64) { |
||||
m.lock.Lock() |
||||
defer m.lock.Unlock() |
||||
m.snapshot.count += n |
||||
m.a1.Update(n) |
||||
m.a5.Update(n) |
||||
m.a15.Update(n) |
||||
m.updateSnapshot() |
||||
} |
||||
|
||||
// Rate1 returns the one-minute moving average rate of events per second.
|
||||
func (m *StandardMeter) Rate1() float64 { |
||||
m.lock.RLock() |
||||
rate1 := m.snapshot.rate1 |
||||
m.lock.RUnlock() |
||||
return rate1 |
||||
} |
||||
|
||||
// Rate5 returns the five-minute moving average rate of events per second.
|
||||
func (m *StandardMeter) Rate5() float64 { |
||||
m.lock.RLock() |
||||
rate5 := m.snapshot.rate5 |
||||
m.lock.RUnlock() |
||||
return rate5 |
||||
} |
||||
|
||||
// Rate15 returns the fifteen-minute moving average rate of events per second.
|
||||
func (m *StandardMeter) Rate15() float64 { |
||||
m.lock.RLock() |
||||
rate15 := m.snapshot.rate15 |
||||
m.lock.RUnlock() |
||||
return rate15 |
||||
} |
||||
|
||||
// RateMean returns the meter's mean rate of events per second.
|
||||
func (m *StandardMeter) RateMean() float64 { |
||||
m.lock.RLock() |
||||
rateMean := m.snapshot.rateMean |
||||
m.lock.RUnlock() |
||||
return rateMean |
||||
} |
||||
|
||||
// Snapshot returns a read-only copy of the meter.
|
||||
func (m *StandardMeter) Snapshot() Meter { |
||||
m.lock.RLock() |
||||
snapshot := *m.snapshot |
||||
m.lock.RUnlock() |
||||
return &snapshot |
||||
} |
||||
|
||||
func (m *StandardMeter) updateSnapshot() { |
||||
// should run with write lock held on m.lock
|
||||
snapshot := m.snapshot |
||||
snapshot.rate1 = m.a1.Rate() |
||||
snapshot.rate5 = m.a5.Rate() |
||||
snapshot.rate15 = m.a15.Rate() |
||||
snapshot.rateMean = float64(snapshot.count) / time.Since(m.startTime).Seconds() |
||||
} |
||||
|
||||
func (m *StandardMeter) tick() { |
||||
m.lock.Lock() |
||||
defer m.lock.Unlock() |
||||
m.a1.Tick() |
||||
m.a5.Tick() |
||||
m.a15.Tick() |
||||
m.updateSnapshot() |
||||
} |
||||
|
||||
type meterArbiter struct { |
||||
sync.RWMutex |
||||
started bool |
||||
meters []*StandardMeter |
||||
ticker *time.Ticker |
||||
} |
||||
|
||||
var arbiter = meterArbiter{ticker: time.NewTicker(5e9)} |
||||
|
||||
// Ticks meters on the scheduled interval
|
||||
func (ma *meterArbiter) tick() { |
||||
for { |
||||
select { |
||||
case <-ma.ticker.C: |
||||
ma.tickMeters() |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (ma *meterArbiter) tickMeters() { |
||||
ma.RLock() |
||||
defer ma.RUnlock() |
||||
for _, meter := range ma.meters { |
||||
meter.tick() |
||||
} |
||||
} |
@ -0,0 +1,60 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func BenchmarkMeter(b *testing.B) { |
||||
m := NewMeter() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
m.Mark(1) |
||||
} |
||||
} |
||||
|
||||
func TestGetOrRegisterMeter(t *testing.T) { |
||||
r := NewRegistry() |
||||
NewRegisteredMeter("foo", r).Mark(47) |
||||
if m := GetOrRegisterMeter("foo", r); 47 != m.Count() { |
||||
t.Fatal(m) |
||||
} |
||||
} |
||||
|
||||
func TestMeterDecay(t *testing.T) { |
||||
ma := meterArbiter{ |
||||
ticker: time.NewTicker(1), |
||||
} |
||||
m := newStandardMeter() |
||||
ma.meters = append(ma.meters, m) |
||||
go ma.tick() |
||||
m.Mark(1) |
||||
rateMean := m.RateMean() |
||||
time.Sleep(1) |
||||
if m.RateMean() >= rateMean { |
||||
t.Error("m.RateMean() didn't decrease") |
||||
} |
||||
} |
||||
|
||||
func TestMeterNonzero(t *testing.T) { |
||||
m := NewMeter() |
||||
m.Mark(3) |
||||
if count := m.Count(); 3 != count { |
||||
t.Errorf("m.Count(): 3 != %v\n", count) |
||||
} |
||||
} |
||||
|
||||
func TestMeterSnapshot(t *testing.T) { |
||||
m := NewMeter() |
||||
m.Mark(1) |
||||
if snapshot := m.Snapshot(); m.RateMean() != snapshot.RateMean() { |
||||
t.Fatal(snapshot) |
||||
} |
||||
} |
||||
|
||||
func TestMeterZero(t *testing.T) { |
||||
m := NewMeter() |
||||
if count := m.Count(); 0 != count { |
||||
t.Errorf("m.Count(): 0 != %v\n", count) |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
// Go port of Coda Hale's Metrics library
|
||||
//
|
||||
// <https://github.com/rcrowley/go-metrics>
|
||||
//
|
||||
// Coda Hale's original work: <https://github.com/codahale/metrics>
|
||||
package metrics |
||||
|
||||
// UseNilMetrics is checked by the constructor functions for all of the
|
||||
// standard metrics. If it is true, the metric returned is a stub.
|
||||
//
|
||||
// This global kill-switch helps quantify the observer effect and makes
|
||||
// for less cluttered pprof profiles.
|
||||
var UseNilMetrics bool = false |
@ -0,0 +1,107 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"io/ioutil" |
||||
"log" |
||||
"sync" |
||||
"testing" |
||||
) |
||||
|
||||
const FANOUT = 128 |
||||
|
||||
// Stop the compiler from complaining during debugging.
|
||||
var ( |
||||
_ = ioutil.Discard |
||||
_ = log.LstdFlags |
||||
) |
||||
|
||||
func BenchmarkMetrics(b *testing.B) { |
||||
r := NewRegistry() |
||||
c := NewRegisteredCounter("counter", r) |
||||
g := NewRegisteredGauge("gauge", r) |
||||
gf := NewRegisteredGaugeFloat64("gaugefloat64", r) |
||||
h := NewRegisteredHistogram("histogram", r, NewUniformSample(100)) |
||||
m := NewRegisteredMeter("meter", r) |
||||
t := NewRegisteredTimer("timer", r) |
||||
RegisterDebugGCStats(r) |
||||
RegisterRuntimeMemStats(r) |
||||
b.ResetTimer() |
||||
ch := make(chan bool) |
||||
|
||||
wgD := &sync.WaitGroup{} |
||||
/* |
||||
wgD.Add(1) |
||||
go func() { |
||||
defer wgD.Done() |
||||
//log.Println("go CaptureDebugGCStats")
|
||||
for { |
||||
select { |
||||
case <-ch: |
||||
//log.Println("done CaptureDebugGCStats")
|
||||
return |
||||
default: |
||||
CaptureDebugGCStatsOnce(r) |
||||
} |
||||
} |
||||
}() |
||||
//*/
|
||||
|
||||
wgR := &sync.WaitGroup{} |
||||
//*
|
||||
wgR.Add(1) |
||||
go func() { |
||||
defer wgR.Done() |
||||
//log.Println("go CaptureRuntimeMemStats")
|
||||
for { |
||||
select { |
||||
case <-ch: |
||||
//log.Println("done CaptureRuntimeMemStats")
|
||||
return |
||||
default: |
||||
CaptureRuntimeMemStatsOnce(r) |
||||
} |
||||
} |
||||
}() |
||||
//*/
|
||||
|
||||
wgW := &sync.WaitGroup{} |
||||
/* |
||||
wgW.Add(1) |
||||
go func() { |
||||
defer wgW.Done() |
||||
//log.Println("go Write")
|
||||
for { |
||||
select { |
||||
case <-ch: |
||||
//log.Println("done Write")
|
||||
return |
||||
default: |
||||
WriteOnce(r, ioutil.Discard) |
||||
} |
||||
} |
||||
}() |
||||
//*/
|
||||
|
||||
wg := &sync.WaitGroup{} |
||||
wg.Add(FANOUT) |
||||
for i := 0; i < FANOUT; i++ { |
||||
go func(i int) { |
||||
defer wg.Done() |
||||
//log.Println("go", i)
|
||||
for i := 0; i < b.N; i++ { |
||||
c.Inc(1) |
||||
g.Update(int64(i)) |
||||
gf.Update(float64(i)) |
||||
h.Update(int64(i)) |
||||
m.Mark(1) |
||||
t.Update(1) |
||||
} |
||||
//log.Println("done", i)
|
||||
}(i) |
||||
} |
||||
wg.Wait() |
||||
close(ch) |
||||
wgD.Wait() |
||||
wgR.Wait() |
||||
wgW.Wait() |
||||
} |
@ -0,0 +1,119 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"log" |
||||
"net" |
||||
"os" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
var shortHostName string = "" |
||||
|
||||
// OpenTSDBConfig provides a container with configuration parameters for
|
||||
// the OpenTSDB exporter
|
||||
type OpenTSDBConfig struct { |
||||
Addr *net.TCPAddr // Network address to connect to
|
||||
Registry Registry // Registry to be exported
|
||||
FlushInterval time.Duration // Flush interval
|
||||
DurationUnit time.Duration // Time conversion unit for durations
|
||||
Prefix string // Prefix to be prepended to metric names
|
||||
} |
||||
|
||||
// OpenTSDB is a blocking exporter function which reports metrics in r
|
||||
// to a TSDB server located at addr, flushing them every d duration
|
||||
// and prepending metric names with prefix.
|
||||
func OpenTSDB(r Registry, d time.Duration, prefix string, addr *net.TCPAddr) { |
||||
OpenTSDBWithConfig(OpenTSDBConfig{ |
||||
Addr: addr, |
||||
Registry: r, |
||||
FlushInterval: d, |
||||
DurationUnit: time.Nanosecond, |
||||
Prefix: prefix, |
||||
}) |
||||
} |
||||
|
||||
// OpenTSDBWithConfig is a blocking exporter function just like OpenTSDB,
|
||||
// but it takes a OpenTSDBConfig instead.
|
||||
func OpenTSDBWithConfig(c OpenTSDBConfig) { |
||||
for _ = range time.Tick(c.FlushInterval) { |
||||
if err := openTSDB(&c); nil != err { |
||||
log.Println(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func getShortHostname() string { |
||||
if shortHostName == "" { |
||||
host, _ := os.Hostname() |
||||
if index := strings.Index(host, "."); index > 0 { |
||||
shortHostName = host[:index] |
||||
} else { |
||||
shortHostName = host |
||||
} |
||||
} |
||||
return shortHostName |
||||
} |
||||
|
||||
func openTSDB(c *OpenTSDBConfig) error { |
||||
shortHostname := getShortHostname() |
||||
now := time.Now().Unix() |
||||
du := float64(c.DurationUnit) |
||||
conn, err := net.DialTCP("tcp", nil, c.Addr) |
||||
if nil != err { |
||||
return err |
||||
} |
||||
defer conn.Close() |
||||
w := bufio.NewWriter(conn) |
||||
c.Registry.Each(func(name string, i interface{}) { |
||||
switch metric := i.(type) { |
||||
case Counter: |
||||
fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, metric.Count(), shortHostname) |
||||
case Gauge: |
||||
fmt.Fprintf(w, "put %s.%s.value %d %d host=%s\n", c.Prefix, name, now, metric.Value(), shortHostname) |
||||
case GaugeFloat64: |
||||
fmt.Fprintf(w, "put %s.%s.value %d %f host=%s\n", c.Prefix, name, now, metric.Value(), shortHostname) |
||||
case Histogram: |
||||
h := metric.Snapshot() |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, h.Count(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.min %d %d host=%s\n", c.Prefix, name, now, h.Min(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.max %d %d host=%s\n", c.Prefix, name, now, h.Max(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.mean %d %.2f host=%s\n", c.Prefix, name, now, h.Mean(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.std-dev %d %.2f host=%s\n", c.Prefix, name, now, h.StdDev(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.50-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[0], shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.75-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[1], shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.95-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[2], shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.99-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[3], shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.999-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[4], shortHostname) |
||||
case Meter: |
||||
m := metric.Snapshot() |
||||
fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, m.Count(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.one-minute %d %.2f host=%s\n", c.Prefix, name, now, m.Rate1(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.five-minute %d %.2f host=%s\n", c.Prefix, name, now, m.Rate5(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.fifteen-minute %d %.2f host=%s\n", c.Prefix, name, now, m.Rate15(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.mean %d %.2f host=%s\n", c.Prefix, name, now, m.RateMean(), shortHostname) |
||||
case Timer: |
||||
t := metric.Snapshot() |
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, t.Count(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.min %d %d host=%s\n", c.Prefix, name, now, t.Min()/int64(du), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.max %d %d host=%s\n", c.Prefix, name, now, t.Max()/int64(du), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.mean %d %.2f host=%s\n", c.Prefix, name, now, t.Mean()/du, shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.std-dev %d %.2f host=%s\n", c.Prefix, name, now, t.StdDev()/du, shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.50-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[0]/du, shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.75-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[1]/du, shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.95-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[2]/du, shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.99-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[3]/du, shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.999-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[4]/du, shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.one-minute %d %.2f host=%s\n", c.Prefix, name, now, t.Rate1(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.five-minute %d %.2f host=%s\n", c.Prefix, name, now, t.Rate5(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.fifteen-minute %d %.2f host=%s\n", c.Prefix, name, now, t.Rate15(), shortHostname) |
||||
fmt.Fprintf(w, "put %s.%s.mean-rate %d %.2f host=%s\n", c.Prefix, name, now, t.RateMean(), shortHostname) |
||||
} |
||||
w.Flush() |
||||
}) |
||||
return nil |
||||
} |
@ -0,0 +1,22 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"net" |
||||
"time" |
||||
) |
||||
|
||||
func ExampleOpenTSDB() { |
||||
addr, _ := net.ResolveTCPAddr("net", ":2003") |
||||
go OpenTSDB(DefaultRegistry, 1*time.Second, "some.prefix", addr) |
||||
} |
||||
|
||||
func ExampleOpenTSDBWithConfig() { |
||||
addr, _ := net.ResolveTCPAddr("net", ":2003") |
||||
go OpenTSDBWithConfig(OpenTSDBConfig{ |
||||
Addr: addr, |
||||
Registry: DefaultRegistry, |
||||
FlushInterval: 1 * time.Second, |
||||
DurationUnit: time.Millisecond, |
||||
}) |
||||
} |
||||
|
@ -0,0 +1,180 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
"sync" |
||||
) |
||||
|
||||
// DuplicateMetric is the error returned by Registry.Register when a metric
|
||||
// already exists. If you mean to Register that metric you must first
|
||||
// Unregister the existing metric.
|
||||
type DuplicateMetric string |
||||
|
||||
func (err DuplicateMetric) Error() string { |
||||
return fmt.Sprintf("duplicate metric: %s", string(err)) |
||||
} |
||||
|
||||
// A Registry holds references to a set of metrics by name and can iterate
|
||||
// over them, calling callback functions provided by the user.
|
||||
//
|
||||
// This is an interface so as to encourage other structs to implement
|
||||
// the Registry API as appropriate.
|
||||
type Registry interface { |
||||
|
||||
// Call the given function for each registered metric.
|
||||
Each(func(string, interface{})) |
||||
|
||||
// Get the metric by the given name or nil if none is registered.
|
||||
Get(string) interface{} |
||||
|
||||
// Gets an existing metric or registers the given one.
|
||||
// The interface can be the metric to register if not found in registry,
|
||||
// or a function returning the metric for lazy instantiation.
|
||||
GetOrRegister(string, interface{}) interface{} |
||||
|
||||
// Register the given metric under the given name.
|
||||
Register(string, interface{}) error |
||||
|
||||
// Run all registered healthchecks.
|
||||
RunHealthchecks() |
||||
|
||||
// Unregister the metric with the given name.
|
||||
Unregister(string) |
||||
|
||||
// Unregister all metrics. (Mostly for testing.)
|
||||
UnregisterAll() |
||||
} |
||||
|
||||
// The standard implementation of a Registry is a mutex-protected map
|
||||
// of names to metrics.
|
||||
type StandardRegistry struct { |
||||
metrics map[string]interface{} |
||||
mutex sync.Mutex |
||||
} |
||||
|
||||
// Create a new registry.
|
||||
func NewRegistry() Registry { |
||||
return &StandardRegistry{metrics: make(map[string]interface{})} |
||||
} |
||||
|
||||
// Call the given function for each registered metric.
|
||||
func (r *StandardRegistry) Each(f func(string, interface{})) { |
||||
for name, i := range r.registered() { |
||||
f(name, i) |
||||
} |
||||
} |
||||
|
||||
// Get the metric by the given name or nil if none is registered.
|
||||
func (r *StandardRegistry) Get(name string) interface{} { |
||||
r.mutex.Lock() |
||||
defer r.mutex.Unlock() |
||||
return r.metrics[name] |
||||
} |
||||
|
||||
// Gets an existing metric or creates and registers a new one. Threadsafe
|
||||
// alternative to calling Get and Register on failure.
|
||||
// The interface can be the metric to register if not found in registry,
|
||||
// or a function returning the metric for lazy instantiation.
|
||||
func (r *StandardRegistry) GetOrRegister(name string, i interface{}) interface{} { |
||||
r.mutex.Lock() |
||||
defer r.mutex.Unlock() |
||||
if metric, ok := r.metrics[name]; ok { |
||||
return metric |
||||
} |
||||
if v := reflect.ValueOf(i); v.Kind() == reflect.Func { |
||||
i = v.Call(nil)[0].Interface() |
||||
} |
||||
r.register(name, i) |
||||
return i |
||||
} |
||||
|
||||
// Register the given metric under the given name. Returns a DuplicateMetric
|
||||
// if a metric by the given name is already registered.
|
||||
func (r *StandardRegistry) Register(name string, i interface{}) error { |
||||
r.mutex.Lock() |
||||
defer r.mutex.Unlock() |
||||
return r.register(name, i) |
||||
} |
||||
|
||||
// Run all registered healthchecks.
|
||||
func (r *StandardRegistry) RunHealthchecks() { |
||||
r.mutex.Lock() |
||||
defer r.mutex.Unlock() |
||||
for _, i := range r.metrics { |
||||
if h, ok := i.(Healthcheck); ok { |
||||
h.Check() |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Unregister the metric with the given name.
|
||||
func (r *StandardRegistry) Unregister(name string) { |
||||
r.mutex.Lock() |
||||
defer r.mutex.Unlock() |
||||
delete(r.metrics, name) |
||||
} |
||||
|
||||
// Unregister all metrics. (Mostly for testing.)
|
||||
func (r *StandardRegistry) UnregisterAll() { |
||||
r.mutex.Lock() |
||||
defer r.mutex.Unlock() |
||||
for name, _ := range r.metrics { |
||||
delete(r.metrics, name) |
||||
} |
||||
} |
||||
|
||||
func (r *StandardRegistry) register(name string, i interface{}) error { |
||||
if _, ok := r.metrics[name]; ok { |
||||
return DuplicateMetric(name) |
||||
} |
||||
switch i.(type) { |
||||
case Counter, Gauge, GaugeFloat64, Healthcheck, Histogram, Meter, Timer: |
||||
r.metrics[name] = i |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (r *StandardRegistry) registered() map[string]interface{} { |
||||
r.mutex.Lock() |
||||
defer r.mutex.Unlock() |
||||
metrics := make(map[string]interface{}, len(r.metrics)) |
||||
for name, i := range r.metrics { |
||||
metrics[name] = i |
||||
} |
||||
return metrics |
||||
} |
||||
|
||||
var DefaultRegistry Registry = NewRegistry() |
||||
|
||||
// Call the given function for each registered metric.
|
||||
func Each(f func(string, interface{})) { |
||||
DefaultRegistry.Each(f) |
||||
} |
||||
|
||||
// Get the metric by the given name or nil if none is registered.
|
||||
func Get(name string) interface{} { |
||||
return DefaultRegistry.Get(name) |
||||
} |
||||
|
||||
// Gets an existing metric or creates and registers a new one. Threadsafe
|
||||
// alternative to calling Get and Register on failure.
|
||||
func GetOrRegister(name string, i interface{}) interface{} { |
||||
return DefaultRegistry.GetOrRegister(name, i) |
||||
} |
||||
|
||||
// Register the given metric under the given name. Returns a DuplicateMetric
|
||||
// if a metric by the given name is already registered.
|
||||
func Register(name string, i interface{}) error { |
||||
return DefaultRegistry.Register(name, i) |
||||
} |
||||
|
||||
// Run all registered healthchecks.
|
||||
func RunHealthchecks() { |
||||
DefaultRegistry.RunHealthchecks() |
||||
} |
||||
|
||||
// Unregister the metric with the given name.
|
||||
func Unregister(name string) { |
||||
DefaultRegistry.Unregister(name) |
||||
} |
@ -0,0 +1,118 @@ |
||||
package metrics |
||||
|
||||
import "testing" |
||||
|
||||
func BenchmarkRegistry(b *testing.B) { |
||||
r := NewRegistry() |
||||
r.Register("foo", NewCounter()) |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
r.Each(func(string, interface{}) {}) |
||||
} |
||||
} |
||||
|
||||
func TestRegistry(t *testing.T) { |
||||
r := NewRegistry() |
||||
r.Register("foo", NewCounter()) |
||||
i := 0 |
||||
r.Each(func(name string, iface interface{}) { |
||||
i++ |
||||
if "foo" != name { |
||||
t.Fatal(name) |
||||
} |
||||
if _, ok := iface.(Counter); !ok { |
||||
t.Fatal(iface) |
||||
} |
||||
}) |
||||
if 1 != i { |
||||
t.Fatal(i) |
||||
} |
||||
r.Unregister("foo") |
||||
i = 0 |
||||
r.Each(func(string, interface{}) { i++ }) |
||||
if 0 != i { |
||||
t.Fatal(i) |
||||
} |
||||
} |
||||
|
||||
func TestRegistryDuplicate(t *testing.T) { |
||||
r := NewRegistry() |
||||
if err := r.Register("foo", NewCounter()); nil != err { |
||||
t.Fatal(err) |
||||
} |
||||
if err := r.Register("foo", NewGauge()); nil == err { |
||||
t.Fatal(err) |
||||
} |
||||
i := 0 |
||||
r.Each(func(name string, iface interface{}) { |
||||
i++ |
||||
if _, ok := iface.(Counter); !ok { |
||||
t.Fatal(iface) |
||||
} |
||||
}) |
||||
if 1 != i { |
||||
t.Fatal(i) |
||||
} |
||||
} |
||||
|
||||
func TestRegistryGet(t *testing.T) { |
||||
r := NewRegistry() |
||||
r.Register("foo", NewCounter()) |
||||
if count := r.Get("foo").(Counter).Count(); 0 != count { |
||||
t.Fatal(count) |
||||
} |
||||
r.Get("foo").(Counter).Inc(1) |
||||
if count := r.Get("foo").(Counter).Count(); 1 != count { |
||||
t.Fatal(count) |
||||
} |
||||
} |
||||
|
||||
func TestRegistryGetOrRegister(t *testing.T) { |
||||
r := NewRegistry() |
||||
|
||||
// First metric wins with GetOrRegister
|
||||
_ = r.GetOrRegister("foo", NewCounter()) |
||||
m := r.GetOrRegister("foo", NewGauge()) |
||||
if _, ok := m.(Counter); !ok { |
||||
t.Fatal(m) |
||||
} |
||||
|
||||
i := 0 |
||||
r.Each(func(name string, iface interface{}) { |
||||
i++ |
||||
if name != "foo" { |
||||
t.Fatal(name) |
||||
} |
||||
if _, ok := iface.(Counter); !ok { |
||||
t.Fatal(iface) |
||||
} |
||||
}) |
||||
if i != 1 { |
||||
t.Fatal(i) |
||||
} |
||||
} |
||||
|
||||
func TestRegistryGetOrRegisterWithLazyInstantiation(t *testing.T) { |
||||
r := NewRegistry() |
||||
|
||||
// First metric wins with GetOrRegister
|
||||
_ = r.GetOrRegister("foo", NewCounter) |
||||
m := r.GetOrRegister("foo", NewGauge) |
||||
if _, ok := m.(Counter); !ok { |
||||
t.Fatal(m) |
||||
} |
||||
|
||||
i := 0 |
||||
r.Each(func(name string, iface interface{}) { |
||||
i++ |
||||
if name != "foo" { |
||||
t.Fatal(name) |
||||
} |
||||
if _, ok := iface.(Counter); !ok { |
||||
t.Fatal(iface) |
||||
} |
||||
}) |
||||
if i != 1 { |
||||
t.Fatal(i) |
||||
} |
||||
} |
@ -0,0 +1,200 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"runtime" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
memStats runtime.MemStats |
||||
runtimeMetrics struct { |
||||
MemStats struct { |
||||
Alloc Gauge |
||||
BuckHashSys Gauge |
||||
DebugGC Gauge |
||||
EnableGC Gauge |
||||
Frees Gauge |
||||
HeapAlloc Gauge |
||||
HeapIdle Gauge |
||||
HeapInuse Gauge |
||||
HeapObjects Gauge |
||||
HeapReleased Gauge |
||||
HeapSys Gauge |
||||
LastGC Gauge |
||||
Lookups Gauge |
||||
Mallocs Gauge |
||||
MCacheInuse Gauge |
||||
MCacheSys Gauge |
||||
MSpanInuse Gauge |
||||
MSpanSys Gauge |
||||
NextGC Gauge |
||||
NumGC Gauge |
||||
PauseNs Histogram |
||||
PauseTotalNs Gauge |
||||
StackInuse Gauge |
||||
StackSys Gauge |
||||
Sys Gauge |
||||
TotalAlloc Gauge |
||||
} |
||||
NumCgoCall Gauge |
||||
NumGoroutine Gauge |
||||
ReadMemStats Timer |
||||
} |
||||
frees uint64 |
||||
lookups uint64 |
||||
mallocs uint64 |
||||
numGC uint32 |
||||
numCgoCalls int64 |
||||
) |
||||
|
||||
// Capture new values for the Go runtime statistics exported in
|
||||
// runtime.MemStats. This is designed to be called as a goroutine.
|
||||
func CaptureRuntimeMemStats(r Registry, d time.Duration) { |
||||
for _ = range time.Tick(d) { |
||||
CaptureRuntimeMemStatsOnce(r) |
||||
} |
||||
} |
||||
|
||||
// Capture new values for the Go runtime statistics exported in
|
||||
// runtime.MemStats. This is designed to be called in a background
|
||||
// goroutine. Giving a registry which has not been given to
|
||||
// RegisterRuntimeMemStats will panic.
|
||||
//
|
||||
// Be very careful with this because runtime.ReadMemStats calls the C
|
||||
// functions runtime·semacquire(&runtime·worldsema) and runtime·stoptheworld()
|
||||
// and that last one does what it says on the tin.
|
||||
func CaptureRuntimeMemStatsOnce(r Registry) { |
||||
t := time.Now() |
||||
runtime.ReadMemStats(&memStats) // This takes 50-200us.
|
||||
runtimeMetrics.ReadMemStats.UpdateSince(t) |
||||
|
||||
runtimeMetrics.MemStats.Alloc.Update(int64(memStats.Alloc)) |
||||
runtimeMetrics.MemStats.BuckHashSys.Update(int64(memStats.BuckHashSys)) |
||||
if memStats.DebugGC { |
||||
runtimeMetrics.MemStats.DebugGC.Update(1) |
||||
} else { |
||||
runtimeMetrics.MemStats.DebugGC.Update(0) |
||||
} |
||||
if memStats.EnableGC { |
||||
runtimeMetrics.MemStats.EnableGC.Update(1) |
||||
} else { |
||||
runtimeMetrics.MemStats.EnableGC.Update(0) |
||||
} |
||||
|
||||
runtimeMetrics.MemStats.Frees.Update(int64(memStats.Frees - frees)) |
||||
runtimeMetrics.MemStats.HeapAlloc.Update(int64(memStats.HeapAlloc)) |
||||
runtimeMetrics.MemStats.HeapIdle.Update(int64(memStats.HeapIdle)) |
||||
runtimeMetrics.MemStats.HeapInuse.Update(int64(memStats.HeapInuse)) |
||||
runtimeMetrics.MemStats.HeapObjects.Update(int64(memStats.HeapObjects)) |
||||
runtimeMetrics.MemStats.HeapReleased.Update(int64(memStats.HeapReleased)) |
||||
runtimeMetrics.MemStats.HeapSys.Update(int64(memStats.HeapSys)) |
||||
runtimeMetrics.MemStats.LastGC.Update(int64(memStats.LastGC)) |
||||
runtimeMetrics.MemStats.Lookups.Update(int64(memStats.Lookups - lookups)) |
||||
runtimeMetrics.MemStats.Mallocs.Update(int64(memStats.Mallocs - mallocs)) |
||||
runtimeMetrics.MemStats.MCacheInuse.Update(int64(memStats.MCacheInuse)) |
||||
runtimeMetrics.MemStats.MCacheSys.Update(int64(memStats.MCacheSys)) |
||||
runtimeMetrics.MemStats.MSpanInuse.Update(int64(memStats.MSpanInuse)) |
||||
runtimeMetrics.MemStats.MSpanSys.Update(int64(memStats.MSpanSys)) |
||||
runtimeMetrics.MemStats.NextGC.Update(int64(memStats.NextGC)) |
||||
runtimeMetrics.MemStats.NumGC.Update(int64(memStats.NumGC - numGC)) |
||||
|
||||
// <https://code.google.com/p/go/source/browse/src/pkg/runtime/mgc0.c>
|
||||
i := numGC % uint32(len(memStats.PauseNs)) |
||||
ii := memStats.NumGC % uint32(len(memStats.PauseNs)) |
||||
if memStats.NumGC-numGC >= uint32(len(memStats.PauseNs)) { |
||||
for i = 0; i < uint32(len(memStats.PauseNs)); i++ { |
||||
runtimeMetrics.MemStats.PauseNs.Update(int64(memStats.PauseNs[i])) |
||||
} |
||||
} else { |
||||
if i > ii { |
||||
for ; i < uint32(len(memStats.PauseNs)); i++ { |
||||
runtimeMetrics.MemStats.PauseNs.Update(int64(memStats.PauseNs[i])) |
||||
} |
||||
i = 0 |
||||
} |
||||
for ; i < ii; i++ { |
||||
runtimeMetrics.MemStats.PauseNs.Update(int64(memStats.PauseNs[i])) |
||||
} |
||||
} |
||||
frees = memStats.Frees |
||||
lookups = memStats.Lookups |
||||
mallocs = memStats.Mallocs |
||||
numGC = memStats.NumGC |
||||
|
||||
runtimeMetrics.MemStats.PauseTotalNs.Update(int64(memStats.PauseTotalNs)) |
||||
runtimeMetrics.MemStats.StackInuse.Update(int64(memStats.StackInuse)) |
||||
runtimeMetrics.MemStats.StackSys.Update(int64(memStats.StackSys)) |
||||
runtimeMetrics.MemStats.Sys.Update(int64(memStats.Sys)) |
||||
runtimeMetrics.MemStats.TotalAlloc.Update(int64(memStats.TotalAlloc)) |
||||
|
||||
currentNumCgoCalls := numCgoCall() |
||||
runtimeMetrics.NumCgoCall.Update(currentNumCgoCalls - numCgoCalls) |
||||
numCgoCalls = currentNumCgoCalls |
||||
|
||||
runtimeMetrics.NumGoroutine.Update(int64(runtime.NumGoroutine())) |
||||
} |
||||
|
||||
// Register runtimeMetrics for the Go runtime statistics exported in runtime and
|
||||
// specifically runtime.MemStats. The runtimeMetrics are named by their
|
||||
// fully-qualified Go symbols, i.e. runtime.MemStats.Alloc.
|
||||
func RegisterRuntimeMemStats(r Registry) { |
||||
runtimeMetrics.MemStats.Alloc = NewGauge() |
||||
runtimeMetrics.MemStats.BuckHashSys = NewGauge() |
||||
runtimeMetrics.MemStats.DebugGC = NewGauge() |
||||
runtimeMetrics.MemStats.EnableGC = NewGauge() |
||||
runtimeMetrics.MemStats.Frees = NewGauge() |
||||
runtimeMetrics.MemStats.HeapAlloc = NewGauge() |
||||
runtimeMetrics.MemStats.HeapIdle = NewGauge() |
||||
runtimeMetrics.MemStats.HeapInuse = NewGauge() |
||||
runtimeMetrics.MemStats.HeapObjects = NewGauge() |
||||
runtimeMetrics.MemStats.HeapReleased = NewGauge() |
||||
runtimeMetrics.MemStats.HeapSys = NewGauge() |
||||
runtimeMetrics.MemStats.LastGC = NewGauge() |
||||
runtimeMetrics.MemStats.Lookups = NewGauge() |
||||
runtimeMetrics.MemStats.Mallocs = NewGauge() |
||||
runtimeMetrics.MemStats.MCacheInuse = NewGauge() |
||||
runtimeMetrics.MemStats.MCacheSys = NewGauge() |
||||
runtimeMetrics.MemStats.MSpanInuse = NewGauge() |
||||
runtimeMetrics.MemStats.MSpanSys = NewGauge() |
||||
runtimeMetrics.MemStats.NextGC = NewGauge() |
||||
runtimeMetrics.MemStats.NumGC = NewGauge() |
||||
runtimeMetrics.MemStats.PauseNs = NewHistogram(NewExpDecaySample(1028, 0.015)) |
||||
runtimeMetrics.MemStats.PauseTotalNs = NewGauge() |
||||
runtimeMetrics.MemStats.StackInuse = NewGauge() |
||||
runtimeMetrics.MemStats.StackSys = NewGauge() |
||||
runtimeMetrics.MemStats.Sys = NewGauge() |
||||
runtimeMetrics.MemStats.TotalAlloc = NewGauge() |
||||
runtimeMetrics.NumCgoCall = NewGauge() |
||||
runtimeMetrics.NumGoroutine = NewGauge() |
||||
runtimeMetrics.ReadMemStats = NewTimer() |
||||
|
||||
r.Register("runtime.MemStats.Alloc", runtimeMetrics.MemStats.Alloc) |
||||
r.Register("runtime.MemStats.BuckHashSys", runtimeMetrics.MemStats.BuckHashSys) |
||||
r.Register("runtime.MemStats.DebugGC", runtimeMetrics.MemStats.DebugGC) |
||||
r.Register("runtime.MemStats.EnableGC", runtimeMetrics.MemStats.EnableGC) |
||||
r.Register("runtime.MemStats.Frees", runtimeMetrics.MemStats.Frees) |
||||
r.Register("runtime.MemStats.HeapAlloc", runtimeMetrics.MemStats.HeapAlloc) |
||||
r.Register("runtime.MemStats.HeapIdle", runtimeMetrics.MemStats.HeapIdle) |
||||
r.Register("runtime.MemStats.HeapInuse", runtimeMetrics.MemStats.HeapInuse) |
||||
r.Register("runtime.MemStats.HeapObjects", runtimeMetrics.MemStats.HeapObjects) |
||||
r.Register("runtime.MemStats.HeapReleased", runtimeMetrics.MemStats.HeapReleased) |
||||
r.Register("runtime.MemStats.HeapSys", runtimeMetrics.MemStats.HeapSys) |
||||
r.Register("runtime.MemStats.LastGC", runtimeMetrics.MemStats.LastGC) |
||||
r.Register("runtime.MemStats.Lookups", runtimeMetrics.MemStats.Lookups) |
||||
r.Register("runtime.MemStats.Mallocs", runtimeMetrics.MemStats.Mallocs) |
||||
r.Register("runtime.MemStats.MCacheInuse", runtimeMetrics.MemStats.MCacheInuse) |
||||
r.Register("runtime.MemStats.MCacheSys", runtimeMetrics.MemStats.MCacheSys) |
||||
r.Register("runtime.MemStats.MSpanInuse", runtimeMetrics.MemStats.MSpanInuse) |
||||
r.Register("runtime.MemStats.MSpanSys", runtimeMetrics.MemStats.MSpanSys) |
||||
r.Register("runtime.MemStats.NextGC", runtimeMetrics.MemStats.NextGC) |
||||
r.Register("runtime.MemStats.NumGC", runtimeMetrics.MemStats.NumGC) |
||||
r.Register("runtime.MemStats.PauseNs", runtimeMetrics.MemStats.PauseNs) |
||||
r.Register("runtime.MemStats.PauseTotalNs", runtimeMetrics.MemStats.PauseTotalNs) |
||||
r.Register("runtime.MemStats.StackInuse", runtimeMetrics.MemStats.StackInuse) |
||||
r.Register("runtime.MemStats.StackSys", runtimeMetrics.MemStats.StackSys) |
||||
r.Register("runtime.MemStats.Sys", runtimeMetrics.MemStats.Sys) |
||||
r.Register("runtime.MemStats.TotalAlloc", runtimeMetrics.MemStats.TotalAlloc) |
||||
r.Register("runtime.NumCgoCall", runtimeMetrics.NumCgoCall) |
||||
r.Register("runtime.NumGoroutine", runtimeMetrics.NumGoroutine) |
||||
r.Register("runtime.ReadMemStats", runtimeMetrics.ReadMemStats) |
||||
} |
@ -0,0 +1,10 @@ |
||||
// +build cgo
|
||||
// +build !appengine
|
||||
|
||||
package metrics |
||||
|
||||
import "runtime" |
||||
|
||||
func numCgoCall() int64 { |
||||
return runtime.NumCgoCall() |
||||
} |
@ -0,0 +1,7 @@ |
||||
// +build !cgo appengine
|
||||
|
||||
package metrics |
||||
|
||||
func numCgoCall() int64 { |
||||
return 0 |
||||
} |
@ -0,0 +1,78 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"runtime" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func BenchmarkRuntimeMemStats(b *testing.B) { |
||||
r := NewRegistry() |
||||
RegisterRuntimeMemStats(r) |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
CaptureRuntimeMemStatsOnce(r) |
||||
} |
||||
} |
||||
|
||||
func TestRuntimeMemStats(t *testing.T) { |
||||
r := NewRegistry() |
||||
RegisterRuntimeMemStats(r) |
||||
CaptureRuntimeMemStatsOnce(r) |
||||
zero := runtimeMetrics.MemStats.PauseNs.Count() // Get a "zero" since GC may have run before these tests.
|
||||
runtime.GC() |
||||
CaptureRuntimeMemStatsOnce(r) |
||||
if count := runtimeMetrics.MemStats.PauseNs.Count(); 1 != count-zero { |
||||
t.Fatal(count - zero) |
||||
} |
||||
runtime.GC() |
||||
runtime.GC() |
||||
CaptureRuntimeMemStatsOnce(r) |
||||
if count := runtimeMetrics.MemStats.PauseNs.Count(); 3 != count-zero { |
||||
t.Fatal(count - zero) |
||||
} |
||||
for i := 0; i < 256; i++ { |
||||
runtime.GC() |
||||
} |
||||
CaptureRuntimeMemStatsOnce(r) |
||||
if count := runtimeMetrics.MemStats.PauseNs.Count(); 259 != count-zero { |
||||
t.Fatal(count - zero) |
||||
} |
||||
for i := 0; i < 257; i++ { |
||||
runtime.GC() |
||||
} |
||||
CaptureRuntimeMemStatsOnce(r) |
||||
if count := runtimeMetrics.MemStats.PauseNs.Count(); 515 != count-zero { // We lost one because there were too many GCs between captures.
|
||||
t.Fatal(count - zero) |
||||
} |
||||
} |
||||
|
||||
func TestRuntimeMemStatsBlocking(t *testing.T) { |
||||
if g := runtime.GOMAXPROCS(0); g < 2 { |
||||
t.Skipf("skipping TestRuntimeMemStatsBlocking with GOMAXPROCS=%d\n", g) |
||||
} |
||||
ch := make(chan int) |
||||
go testRuntimeMemStatsBlocking(ch) |
||||
var memStats runtime.MemStats |
||||
t0 := time.Now() |
||||
runtime.ReadMemStats(&memStats) |
||||
t1 := time.Now() |
||||
t.Log("i++ during runtime.ReadMemStats:", <-ch) |
||||
go testRuntimeMemStatsBlocking(ch) |
||||
d := t1.Sub(t0) |
||||
t.Log(d) |
||||
time.Sleep(d) |
||||
t.Log("i++ during time.Sleep:", <-ch) |
||||
} |
||||
|
||||
func testRuntimeMemStatsBlocking(ch chan int) { |
||||
i := 0 |
||||
for { |
||||
select { |
||||
case ch <- i: |
||||
return |
||||
default: |
||||
i++ |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,609 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"math" |
||||
"math/rand" |
||||
"sort" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
const rescaleThreshold = time.Hour |
||||
|
||||
// Samples maintain a statistically-significant selection of values from
|
||||
// a stream.
|
||||
type Sample interface { |
||||
Clear() |
||||
Count() int64 |
||||
Max() int64 |
||||
Mean() float64 |
||||
Min() int64 |
||||
Percentile(float64) float64 |
||||
Percentiles([]float64) []float64 |
||||
Size() int |
||||
Snapshot() Sample |
||||
StdDev() float64 |
||||
Sum() int64 |
||||
Update(int64) |
||||
Values() []int64 |
||||
Variance() float64 |
||||
} |
||||
|
||||
// ExpDecaySample is an exponentially-decaying sample using a forward-decaying
|
||||
// priority reservoir. See Cormode et al's "Forward Decay: A Practical Time
|
||||
// Decay Model for Streaming Systems".
|
||||
//
|
||||
// <http://www.research.att.com/people/Cormode_Graham/library/publications/CormodeShkapenyukSrivastavaXu09.pdf>
|
||||
type ExpDecaySample struct { |
||||
alpha float64 |
||||
count int64 |
||||
mutex sync.Mutex |
||||
reservoirSize int |
||||
t0, t1 time.Time |
||||
values *expDecaySampleHeap |
||||
} |
||||
|
||||
// NewExpDecaySample constructs a new exponentially-decaying sample with the
|
||||
// given reservoir size and alpha.
|
||||
func NewExpDecaySample(reservoirSize int, alpha float64) Sample { |
||||
if UseNilMetrics { |
||||
return NilSample{} |
||||
} |
||||
s := &ExpDecaySample{ |
||||
alpha: alpha, |
||||
reservoirSize: reservoirSize, |
||||
t0: time.Now(), |
||||
values: newExpDecaySampleHeap(reservoirSize), |
||||
} |
||||
s.t1 = s.t0.Add(rescaleThreshold) |
||||
return s |
||||
} |
||||
|
||||
// Clear clears all samples.
|
||||
func (s *ExpDecaySample) Clear() { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
s.count = 0 |
||||
s.t0 = time.Now() |
||||
s.t1 = s.t0.Add(rescaleThreshold) |
||||
s.values.Clear() |
||||
} |
||||
|
||||
// Count returns the number of samples recorded, which may exceed the
|
||||
// reservoir size.
|
||||
func (s *ExpDecaySample) Count() int64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return s.count |
||||
} |
||||
|
||||
// Max returns the maximum value in the sample, which may not be the maximum
|
||||
// value ever to be part of the sample.
|
||||
func (s *ExpDecaySample) Max() int64 { |
||||
return SampleMax(s.Values()) |
||||
} |
||||
|
||||
// Mean returns the mean of the values in the sample.
|
||||
func (s *ExpDecaySample) Mean() float64 { |
||||
return SampleMean(s.Values()) |
||||
} |
||||
|
||||
// Min returns the minimum value in the sample, which may not be the minimum
|
||||
// value ever to be part of the sample.
|
||||
func (s *ExpDecaySample) Min() int64 { |
||||
return SampleMin(s.Values()) |
||||
} |
||||
|
||||
// Percentile returns an arbitrary percentile of values in the sample.
|
||||
func (s *ExpDecaySample) Percentile(p float64) float64 { |
||||
return SamplePercentile(s.Values(), p) |
||||
} |
||||
|
||||
// Percentiles returns a slice of arbitrary percentiles of values in the
|
||||
// sample.
|
||||
func (s *ExpDecaySample) Percentiles(ps []float64) []float64 { |
||||
return SamplePercentiles(s.Values(), ps) |
||||
} |
||||
|
||||
// Size returns the size of the sample, which is at most the reservoir size.
|
||||
func (s *ExpDecaySample) Size() int { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return s.values.Size() |
||||
} |
||||
|
||||
// Snapshot returns a read-only copy of the sample.
|
||||
func (s *ExpDecaySample) Snapshot() Sample { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
vals := s.values.Values() |
||||
values := make([]int64, len(vals)) |
||||
for i, v := range vals { |
||||
values[i] = v.v |
||||
} |
||||
return &SampleSnapshot{ |
||||
count: s.count, |
||||
values: values, |
||||
} |
||||
} |
||||
|
||||
// StdDev returns the standard deviation of the values in the sample.
|
||||
func (s *ExpDecaySample) StdDev() float64 { |
||||
return SampleStdDev(s.Values()) |
||||
} |
||||
|
||||
// Sum returns the sum of the values in the sample.
|
||||
func (s *ExpDecaySample) Sum() int64 { |
||||
return SampleSum(s.Values()) |
||||
} |
||||
|
||||
// Update samples a new value.
|
||||
func (s *ExpDecaySample) Update(v int64) { |
||||
s.update(time.Now(), v) |
||||
} |
||||
|
||||
// Values returns a copy of the values in the sample.
|
||||
func (s *ExpDecaySample) Values() []int64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
vals := s.values.Values() |
||||
values := make([]int64, len(vals)) |
||||
for i, v := range vals { |
||||
values[i] = v.v |
||||
} |
||||
return values |
||||
} |
||||
|
||||
// Variance returns the variance of the values in the sample.
|
||||
func (s *ExpDecaySample) Variance() float64 { |
||||
return SampleVariance(s.Values()) |
||||
} |
||||
|
||||
// update samples a new value at a particular timestamp. This is a method all
|
||||
// its own to facilitate testing.
|
||||
func (s *ExpDecaySample) update(t time.Time, v int64) { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
s.count++ |
||||
if s.values.Size() == s.reservoirSize { |
||||
s.values.Pop() |
||||
} |
||||
s.values.Push(expDecaySample{ |
||||
k: math.Exp(t.Sub(s.t0).Seconds()*s.alpha) / rand.Float64(), |
||||
v: v, |
||||
}) |
||||
if t.After(s.t1) { |
||||
values := s.values.Values() |
||||
t0 := s.t0 |
||||
s.values.Clear() |
||||
s.t0 = t |
||||
s.t1 = s.t0.Add(rescaleThreshold) |
||||
for _, v := range values { |
||||
v.k = v.k * math.Exp(-s.alpha*s.t0.Sub(t0).Seconds()) |
||||
s.values.Push(v) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// NilSample is a no-op Sample.
|
||||
type NilSample struct{} |
||||
|
||||
// Clear is a no-op.
|
||||
func (NilSample) Clear() {} |
||||
|
||||
// Count is a no-op.
|
||||
func (NilSample) Count() int64 { return 0 } |
||||
|
||||
// Max is a no-op.
|
||||
func (NilSample) Max() int64 { return 0 } |
||||
|
||||
// Mean is a no-op.
|
||||
func (NilSample) Mean() float64 { return 0.0 } |
||||
|
||||
// Min is a no-op.
|
||||
func (NilSample) Min() int64 { return 0 } |
||||
|
||||
// Percentile is a no-op.
|
||||
func (NilSample) Percentile(p float64) float64 { return 0.0 } |
||||
|
||||
// Percentiles is a no-op.
|
||||
func (NilSample) Percentiles(ps []float64) []float64 { |
||||
return make([]float64, len(ps)) |
||||
} |
||||
|
||||
// Size is a no-op.
|
||||
func (NilSample) Size() int { return 0 } |
||||
|
||||
// Sample is a no-op.
|
||||
func (NilSample) Snapshot() Sample { return NilSample{} } |
||||
|
||||
// StdDev is a no-op.
|
||||
func (NilSample) StdDev() float64 { return 0.0 } |
||||
|
||||
// Sum is a no-op.
|
||||
func (NilSample) Sum() int64 { return 0 } |
||||
|
||||
// Update is a no-op.
|
||||
func (NilSample) Update(v int64) {} |
||||
|
||||
// Values is a no-op.
|
||||
func (NilSample) Values() []int64 { return []int64{} } |
||||
|
||||
// Variance is a no-op.
|
||||
func (NilSample) Variance() float64 { return 0.0 } |
||||
|
||||
// SampleMax returns the maximum value of the slice of int64.
|
||||
func SampleMax(values []int64) int64 { |
||||
if 0 == len(values) { |
||||
return 0 |
||||
} |
||||
var max int64 = math.MinInt64 |
||||
for _, v := range values { |
||||
if max < v { |
||||
max = v |
||||
} |
||||
} |
||||
return max |
||||
} |
||||
|
||||
// SampleMean returns the mean value of the slice of int64.
|
||||
func SampleMean(values []int64) float64 { |
||||
if 0 == len(values) { |
||||
return 0.0 |
||||
} |
||||
return float64(SampleSum(values)) / float64(len(values)) |
||||
} |
||||
|
||||
// SampleMin returns the minimum value of the slice of int64.
|
||||
func SampleMin(values []int64) int64 { |
||||
if 0 == len(values) { |
||||
return 0 |
||||
} |
||||
var min int64 = math.MaxInt64 |
||||
for _, v := range values { |
||||
if min > v { |
||||
min = v |
||||
} |
||||
} |
||||
return min |
||||
} |
||||
|
||||
// SamplePercentiles returns an arbitrary percentile of the slice of int64.
|
||||
func SamplePercentile(values int64Slice, p float64) float64 { |
||||
return SamplePercentiles(values, []float64{p})[0] |
||||
} |
||||
|
||||
// SamplePercentiles returns a slice of arbitrary percentiles of the slice of
|
||||
// int64.
|
||||
func SamplePercentiles(values int64Slice, ps []float64) []float64 { |
||||
scores := make([]float64, len(ps)) |
||||
size := len(values) |
||||
if size > 0 { |
||||
sort.Sort(values) |
||||
for i, p := range ps { |
||||
pos := p * float64(size+1) |
||||
if pos < 1.0 { |
||||
scores[i] = float64(values[0]) |
||||
} else if pos >= float64(size) { |
||||
scores[i] = float64(values[size-1]) |
||||
} else { |
||||
lower := float64(values[int(pos)-1]) |
||||
upper := float64(values[int(pos)]) |
||||
scores[i] = lower + (pos-math.Floor(pos))*(upper-lower) |
||||
} |
||||
} |
||||
} |
||||
return scores |
||||
} |
||||
|
||||
// SampleSnapshot is a read-only copy of another Sample.
|
||||
type SampleSnapshot struct { |
||||
count int64 |
||||
values []int64 |
||||
} |
||||
|
||||
// Clear panics.
|
||||
func (*SampleSnapshot) Clear() { |
||||
panic("Clear called on a SampleSnapshot") |
||||
} |
||||
|
||||
// Count returns the count of inputs at the time the snapshot was taken.
|
||||
func (s *SampleSnapshot) Count() int64 { return s.count } |
||||
|
||||
// Max returns the maximal value at the time the snapshot was taken.
|
||||
func (s *SampleSnapshot) Max() int64 { return SampleMax(s.values) } |
||||
|
||||
// Mean returns the mean value at the time the snapshot was taken.
|
||||
func (s *SampleSnapshot) Mean() float64 { return SampleMean(s.values) } |
||||
|
||||
// Min returns the minimal value at the time the snapshot was taken.
|
||||
func (s *SampleSnapshot) Min() int64 { return SampleMin(s.values) } |
||||
|
||||
// Percentile returns an arbitrary percentile of values at the time the
|
||||
// snapshot was taken.
|
||||
func (s *SampleSnapshot) Percentile(p float64) float64 { |
||||
return SamplePercentile(s.values, p) |
||||
} |
||||
|
||||
// Percentiles returns a slice of arbitrary percentiles of values at the time
|
||||
// the snapshot was taken.
|
||||
func (s *SampleSnapshot) Percentiles(ps []float64) []float64 { |
||||
return SamplePercentiles(s.values, ps) |
||||
} |
||||
|
||||
// Size returns the size of the sample at the time the snapshot was taken.
|
||||
func (s *SampleSnapshot) Size() int { return len(s.values) } |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (s *SampleSnapshot) Snapshot() Sample { return s } |
||||
|
||||
// StdDev returns the standard deviation of values at the time the snapshot was
|
||||
// taken.
|
||||
func (s *SampleSnapshot) StdDev() float64 { return SampleStdDev(s.values) } |
||||
|
||||
// Sum returns the sum of values at the time the snapshot was taken.
|
||||
func (s *SampleSnapshot) Sum() int64 { return SampleSum(s.values) } |
||||
|
||||
// Update panics.
|
||||
func (*SampleSnapshot) Update(int64) { |
||||
panic("Update called on a SampleSnapshot") |
||||
} |
||||
|
||||
// Values returns a copy of the values in the sample.
|
||||
func (s *SampleSnapshot) Values() []int64 { |
||||
values := make([]int64, len(s.values)) |
||||
copy(values, s.values) |
||||
return values |
||||
} |
||||
|
||||
// Variance returns the variance of values at the time the snapshot was taken.
|
||||
func (s *SampleSnapshot) Variance() float64 { return SampleVariance(s.values) } |
||||
|
||||
// SampleStdDev returns the standard deviation of the slice of int64.
|
||||
func SampleStdDev(values []int64) float64 { |
||||
return math.Sqrt(SampleVariance(values)) |
||||
} |
||||
|
||||
// SampleSum returns the sum of the slice of int64.
|
||||
func SampleSum(values []int64) int64 { |
||||
var sum int64 |
||||
for _, v := range values { |
||||
sum += v |
||||
} |
||||
return sum |
||||
} |
||||
|
||||
// SampleVariance returns the variance of the slice of int64.
|
||||
func SampleVariance(values []int64) float64 { |
||||
if 0 == len(values) { |
||||
return 0.0 |
||||
} |
||||
m := SampleMean(values) |
||||
var sum float64 |
||||
for _, v := range values { |
||||
d := float64(v) - m |
||||
sum += d * d |
||||
} |
||||
return sum / float64(len(values)) |
||||
} |
||||
|
||||
// A uniform sample using Vitter's Algorithm R.
|
||||
//
|
||||
// <http://www.cs.umd.edu/~samir/498/vitter.pdf>
|
||||
type UniformSample struct { |
||||
count int64 |
||||
mutex sync.Mutex |
||||
reservoirSize int |
||||
values []int64 |
||||
} |
||||
|
||||
// NewUniformSample constructs a new uniform sample with the given reservoir
|
||||
// size.
|
||||
func NewUniformSample(reservoirSize int) Sample { |
||||
if UseNilMetrics { |
||||
return NilSample{} |
||||
} |
||||
return &UniformSample{ |
||||
reservoirSize: reservoirSize, |
||||
values: make([]int64, 0, reservoirSize), |
||||
} |
||||
} |
||||
|
||||
// Clear clears all samples.
|
||||
func (s *UniformSample) Clear() { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
s.count = 0 |
||||
s.values = make([]int64, 0, s.reservoirSize) |
||||
} |
||||
|
||||
// Count returns the number of samples recorded, which may exceed the
|
||||
// reservoir size.
|
||||
func (s *UniformSample) Count() int64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return s.count |
||||
} |
||||
|
||||
// Max returns the maximum value in the sample, which may not be the maximum
|
||||
// value ever to be part of the sample.
|
||||
func (s *UniformSample) Max() int64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return SampleMax(s.values) |
||||
} |
||||
|
||||
// Mean returns the mean of the values in the sample.
|
||||
func (s *UniformSample) Mean() float64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return SampleMean(s.values) |
||||
} |
||||
|
||||
// Min returns the minimum value in the sample, which may not be the minimum
|
||||
// value ever to be part of the sample.
|
||||
func (s *UniformSample) Min() int64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return SampleMin(s.values) |
||||
} |
||||
|
||||
// Percentile returns an arbitrary percentile of values in the sample.
|
||||
func (s *UniformSample) Percentile(p float64) float64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return SamplePercentile(s.values, p) |
||||
} |
||||
|
||||
// Percentiles returns a slice of arbitrary percentiles of values in the
|
||||
// sample.
|
||||
func (s *UniformSample) Percentiles(ps []float64) []float64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return SamplePercentiles(s.values, ps) |
||||
} |
||||
|
||||
// Size returns the size of the sample, which is at most the reservoir size.
|
||||
func (s *UniformSample) Size() int { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return len(s.values) |
||||
} |
||||
|
||||
// Snapshot returns a read-only copy of the sample.
|
||||
func (s *UniformSample) Snapshot() Sample { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
values := make([]int64, len(s.values)) |
||||
copy(values, s.values) |
||||
return &SampleSnapshot{ |
||||
count: s.count, |
||||
values: values, |
||||
} |
||||
} |
||||
|
||||
// StdDev returns the standard deviation of the values in the sample.
|
||||
func (s *UniformSample) StdDev() float64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return SampleStdDev(s.values) |
||||
} |
||||
|
||||
// Sum returns the sum of the values in the sample.
|
||||
func (s *UniformSample) Sum() int64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return SampleSum(s.values) |
||||
} |
||||
|
||||
// Update samples a new value.
|
||||
func (s *UniformSample) Update(v int64) { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
s.count++ |
||||
if len(s.values) < s.reservoirSize { |
||||
s.values = append(s.values, v) |
||||
} else { |
||||
r := rand.Int63n(s.count) |
||||
if r < int64(len(s.values)) { |
||||
s.values[int(r)] = v |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Values returns a copy of the values in the sample.
|
||||
func (s *UniformSample) Values() []int64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
values := make([]int64, len(s.values)) |
||||
copy(values, s.values) |
||||
return values |
||||
} |
||||
|
||||
// Variance returns the variance of the values in the sample.
|
||||
func (s *UniformSample) Variance() float64 { |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
return SampleVariance(s.values) |
||||
} |
||||
|
||||
// expDecaySample represents an individual sample in a heap.
|
||||
type expDecaySample struct { |
||||
k float64 |
||||
v int64 |
||||
} |
||||
|
||||
func newExpDecaySampleHeap(reservoirSize int) *expDecaySampleHeap { |
||||
return &expDecaySampleHeap{make([]expDecaySample, 0, reservoirSize)} |
||||
} |
||||
|
||||
// expDecaySampleHeap is a min-heap of expDecaySamples.
|
||||
// The internal implementation is copied from the standard library's container/heap
|
||||
type expDecaySampleHeap struct { |
||||
s []expDecaySample |
||||
} |
||||
|
||||
func (h *expDecaySampleHeap) Clear() { |
||||
h.s = h.s[:0] |
||||
} |
||||
|
||||
func (h *expDecaySampleHeap) Push(s expDecaySample) { |
||||
n := len(h.s) |
||||
h.s = h.s[0 : n+1] |
||||
h.s[n] = s |
||||
h.up(n) |
||||
} |
||||
|
||||
func (h *expDecaySampleHeap) Pop() expDecaySample { |
||||
n := len(h.s) - 1 |
||||
h.s[0], h.s[n] = h.s[n], h.s[0] |
||||
h.down(0, n) |
||||
|
||||
n = len(h.s) |
||||
s := h.s[n-1] |
||||
h.s = h.s[0 : n-1] |
||||
return s |
||||
} |
||||
|
||||
func (h *expDecaySampleHeap) Size() int { |
||||
return len(h.s) |
||||
} |
||||
|
||||
func (h *expDecaySampleHeap) Values() []expDecaySample { |
||||
return h.s |
||||
} |
||||
|
||||
func (h *expDecaySampleHeap) up(j int) { |
||||
for { |
||||
i := (j - 1) / 2 // parent
|
||||
if i == j || !(h.s[j].k < h.s[i].k) { |
||||
break |
||||
} |
||||
h.s[i], h.s[j] = h.s[j], h.s[i] |
||||
j = i |
||||
} |
||||
} |
||||
|
||||
func (h *expDecaySampleHeap) down(i, n int) { |
||||
for { |
||||
j1 := 2*i + 1 |
||||
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
|
||||
break |
||||
} |
||||
j := j1 // left child
|
||||
if j2 := j1 + 1; j2 < n && !(h.s[j1].k < h.s[j2].k) { |
||||
j = j2 // = 2*i + 2 // right child
|
||||
} |
||||
if !(h.s[j].k < h.s[i].k) { |
||||
break |
||||
} |
||||
h.s[i], h.s[j] = h.s[j], h.s[i] |
||||
i = j |
||||
} |
||||
} |
||||
|
||||
type int64Slice []int64 |
||||
|
||||
func (p int64Slice) Len() int { return len(p) } |
||||
func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] } |
||||
func (p int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |
@ -0,0 +1,363 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"math/rand" |
||||
"runtime" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
// Benchmark{Compute,Copy}{1000,1000000} demonstrate that, even for relatively
|
||||
// expensive computations like Variance, the cost of copying the Sample, as
|
||||
// approximated by a make and copy, is much greater than the cost of the
|
||||
// computation for small samples and only slightly less for large samples.
|
||||
func BenchmarkCompute1000(b *testing.B) { |
||||
s := make([]int64, 1000) |
||||
for i := 0; i < len(s); i++ { |
||||
s[i] = int64(i) |
||||
} |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
SampleVariance(s) |
||||
} |
||||
} |
||||
func BenchmarkCompute1000000(b *testing.B) { |
||||
s := make([]int64, 1000000) |
||||
for i := 0; i < len(s); i++ { |
||||
s[i] = int64(i) |
||||
} |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
SampleVariance(s) |
||||
} |
||||
} |
||||
func BenchmarkCopy1000(b *testing.B) { |
||||
s := make([]int64, 1000) |
||||
for i := 0; i < len(s); i++ { |
||||
s[i] = int64(i) |
||||
} |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
sCopy := make([]int64, len(s)) |
||||
copy(sCopy, s) |
||||
} |
||||
} |
||||
func BenchmarkCopy1000000(b *testing.B) { |
||||
s := make([]int64, 1000000) |
||||
for i := 0; i < len(s); i++ { |
||||
s[i] = int64(i) |
||||
} |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
sCopy := make([]int64, len(s)) |
||||
copy(sCopy, s) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkExpDecaySample257(b *testing.B) { |
||||
benchmarkSample(b, NewExpDecaySample(257, 0.015)) |
||||
} |
||||
|
||||
func BenchmarkExpDecaySample514(b *testing.B) { |
||||
benchmarkSample(b, NewExpDecaySample(514, 0.015)) |
||||
} |
||||
|
||||
func BenchmarkExpDecaySample1028(b *testing.B) { |
||||
benchmarkSample(b, NewExpDecaySample(1028, 0.015)) |
||||
} |
||||
|
||||
func BenchmarkUniformSample257(b *testing.B) { |
||||
benchmarkSample(b, NewUniformSample(257)) |
||||
} |
||||
|
||||
func BenchmarkUniformSample514(b *testing.B) { |
||||
benchmarkSample(b, NewUniformSample(514)) |
||||
} |
||||
|
||||
func BenchmarkUniformSample1028(b *testing.B) { |
||||
benchmarkSample(b, NewUniformSample(1028)) |
||||
} |
||||
|
||||
func TestExpDecaySample10(t *testing.T) { |
||||
rand.Seed(1) |
||||
s := NewExpDecaySample(100, 0.99) |
||||
for i := 0; i < 10; i++ { |
||||
s.Update(int64(i)) |
||||
} |
||||
if size := s.Count(); 10 != size { |
||||
t.Errorf("s.Count(): 10 != %v\n", size) |
||||
} |
||||
if size := s.Size(); 10 != size { |
||||
t.Errorf("s.Size(): 10 != %v\n", size) |
||||
} |
||||
if l := len(s.Values()); 10 != l { |
||||
t.Errorf("len(s.Values()): 10 != %v\n", l) |
||||
} |
||||
for _, v := range s.Values() { |
||||
if v > 10 || v < 0 { |
||||
t.Errorf("out of range [0, 10): %v\n", v) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestExpDecaySample100(t *testing.T) { |
||||
rand.Seed(1) |
||||
s := NewExpDecaySample(1000, 0.01) |
||||
for i := 0; i < 100; i++ { |
||||
s.Update(int64(i)) |
||||
} |
||||
if size := s.Count(); 100 != size { |
||||
t.Errorf("s.Count(): 100 != %v\n", size) |
||||
} |
||||
if size := s.Size(); 100 != size { |
||||
t.Errorf("s.Size(): 100 != %v\n", size) |
||||
} |
||||
if l := len(s.Values()); 100 != l { |
||||
t.Errorf("len(s.Values()): 100 != %v\n", l) |
||||
} |
||||
for _, v := range s.Values() { |
||||
if v > 100 || v < 0 { |
||||
t.Errorf("out of range [0, 100): %v\n", v) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestExpDecaySample1000(t *testing.T) { |
||||
rand.Seed(1) |
||||
s := NewExpDecaySample(100, 0.99) |
||||
for i := 0; i < 1000; i++ { |
||||
s.Update(int64(i)) |
||||
} |
||||
if size := s.Count(); 1000 != size { |
||||
t.Errorf("s.Count(): 1000 != %v\n", size) |
||||
} |
||||
if size := s.Size(); 100 != size { |
||||
t.Errorf("s.Size(): 100 != %v\n", size) |
||||
} |
||||
if l := len(s.Values()); 100 != l { |
||||
t.Errorf("len(s.Values()): 100 != %v\n", l) |
||||
} |
||||
for _, v := range s.Values() { |
||||
if v > 1000 || v < 0 { |
||||
t.Errorf("out of range [0, 1000): %v\n", v) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// This test makes sure that the sample's priority is not amplified by using
|
||||
// nanosecond duration since start rather than second duration since start.
|
||||
// The priority becomes +Inf quickly after starting if this is done,
|
||||
// effectively freezing the set of samples until a rescale step happens.
|
||||
func TestExpDecaySampleNanosecondRegression(t *testing.T) { |
||||
rand.Seed(1) |
||||
s := NewExpDecaySample(100, 0.99) |
||||
for i := 0; i < 100; i++ { |
||||
s.Update(10) |
||||
} |
||||
time.Sleep(1 * time.Millisecond) |
||||
for i := 0; i < 100; i++ { |
||||
s.Update(20) |
||||
} |
||||
v := s.Values() |
||||
avg := float64(0) |
||||
for i := 0; i < len(v); i++ { |
||||
avg += float64(v[i]) |
||||
} |
||||
avg /= float64(len(v)) |
||||
if avg > 16 || avg < 14 { |
||||
t.Errorf("out of range [14, 16]: %v\n", avg) |
||||
} |
||||
} |
||||
|
||||
func TestExpDecaySampleRescale(t *testing.T) { |
||||
s := NewExpDecaySample(2, 0.001).(*ExpDecaySample) |
||||
s.update(time.Now(), 1) |
||||
s.update(time.Now().Add(time.Hour+time.Microsecond), 1) |
||||
for _, v := range s.values.Values() { |
||||
if v.k == 0.0 { |
||||
t.Fatal("v.k == 0.0") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestExpDecaySampleSnapshot(t *testing.T) { |
||||
now := time.Now() |
||||
rand.Seed(1) |
||||
s := NewExpDecaySample(100, 0.99) |
||||
for i := 1; i <= 10000; i++ { |
||||
s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i)) |
||||
} |
||||
snapshot := s.Snapshot() |
||||
s.Update(1) |
||||
testExpDecaySampleStatistics(t, snapshot) |
||||
} |
||||
|
||||
func TestExpDecaySampleStatistics(t *testing.T) { |
||||
now := time.Now() |
||||
rand.Seed(1) |
||||
s := NewExpDecaySample(100, 0.99) |
||||
for i := 1; i <= 10000; i++ { |
||||
s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i)) |
||||
} |
||||
testExpDecaySampleStatistics(t, s) |
||||
} |
||||
|
||||
func TestUniformSample(t *testing.T) { |
||||
rand.Seed(1) |
||||
s := NewUniformSample(100) |
||||
for i := 0; i < 1000; i++ { |
||||
s.Update(int64(i)) |
||||
} |
||||
if size := s.Count(); 1000 != size { |
||||
t.Errorf("s.Count(): 1000 != %v\n", size) |
||||
} |
||||
if size := s.Size(); 100 != size { |
||||
t.Errorf("s.Size(): 100 != %v\n", size) |
||||
} |
||||
if l := len(s.Values()); 100 != l { |
||||
t.Errorf("len(s.Values()): 100 != %v\n", l) |
||||
} |
||||
for _, v := range s.Values() { |
||||
if v > 1000 || v < 0 { |
||||
t.Errorf("out of range [0, 100): %v\n", v) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestUniformSampleIncludesTail(t *testing.T) { |
||||
rand.Seed(1) |
||||
s := NewUniformSample(100) |
||||
max := 100 |
||||
for i := 0; i < max; i++ { |
||||
s.Update(int64(i)) |
||||
} |
||||
v := s.Values() |
||||
sum := 0 |
||||
exp := (max - 1) * max / 2 |
||||
for i := 0; i < len(v); i++ { |
||||
sum += int(v[i]) |
||||
} |
||||
if exp != sum { |
||||
t.Errorf("sum: %v != %v\n", exp, sum) |
||||
} |
||||
} |
||||
|
||||
func TestUniformSampleSnapshot(t *testing.T) { |
||||
s := NewUniformSample(100) |
||||
for i := 1; i <= 10000; i++ { |
||||
s.Update(int64(i)) |
||||
} |
||||
snapshot := s.Snapshot() |
||||
s.Update(1) |
||||
testUniformSampleStatistics(t, snapshot) |
||||
} |
||||
|
||||
func TestUniformSampleStatistics(t *testing.T) { |
||||
rand.Seed(1) |
||||
s := NewUniformSample(100) |
||||
for i := 1; i <= 10000; i++ { |
||||
s.Update(int64(i)) |
||||
} |
||||
testUniformSampleStatistics(t, s) |
||||
} |
||||
|
||||
func benchmarkSample(b *testing.B, s Sample) { |
||||
var memStats runtime.MemStats |
||||
runtime.ReadMemStats(&memStats) |
||||
pauseTotalNs := memStats.PauseTotalNs |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
s.Update(1) |
||||
} |
||||
b.StopTimer() |
||||
runtime.GC() |
||||
runtime.ReadMemStats(&memStats) |
||||
b.Logf("GC cost: %d ns/op", int(memStats.PauseTotalNs-pauseTotalNs)/b.N) |
||||
} |
||||
|
||||
func testExpDecaySampleStatistics(t *testing.T, s Sample) { |
||||
if count := s.Count(); 10000 != count { |
||||
t.Errorf("s.Count(): 10000 != %v\n", count) |
||||
} |
||||
if min := s.Min(); 107 != min { |
||||
t.Errorf("s.Min(): 107 != %v\n", min) |
||||
} |
||||
if max := s.Max(); 10000 != max { |
||||
t.Errorf("s.Max(): 10000 != %v\n", max) |
||||
} |
||||
if mean := s.Mean(); 4965.98 != mean { |
||||
t.Errorf("s.Mean(): 4965.98 != %v\n", mean) |
||||
} |
||||
if stdDev := s.StdDev(); 2959.825156930727 != stdDev { |
||||
t.Errorf("s.StdDev(): 2959.825156930727 != %v\n", stdDev) |
||||
} |
||||
ps := s.Percentiles([]float64{0.5, 0.75, 0.99}) |
||||
if 4615 != ps[0] { |
||||
t.Errorf("median: 4615 != %v\n", ps[0]) |
||||
} |
||||
if 7672 != ps[1] { |
||||
t.Errorf("75th percentile: 7672 != %v\n", ps[1]) |
||||
} |
||||
if 9998.99 != ps[2] { |
||||
t.Errorf("99th percentile: 9998.99 != %v\n", ps[2]) |
||||
} |
||||
} |
||||
|
||||
func testUniformSampleStatistics(t *testing.T, s Sample) { |
||||
if count := s.Count(); 10000 != count { |
||||
t.Errorf("s.Count(): 10000 != %v\n", count) |
||||
} |
||||
if min := s.Min(); 37 != min { |
||||
t.Errorf("s.Min(): 37 != %v\n", min) |
||||
} |
||||
if max := s.Max(); 9989 != max { |
||||
t.Errorf("s.Max(): 9989 != %v\n", max) |
||||
} |
||||
if mean := s.Mean(); 4748.14 != mean { |
||||
t.Errorf("s.Mean(): 4748.14 != %v\n", mean) |
||||
} |
||||
if stdDev := s.StdDev(); 2826.684117548333 != stdDev { |
||||
t.Errorf("s.StdDev(): 2826.684117548333 != %v\n", stdDev) |
||||
} |
||||
ps := s.Percentiles([]float64{0.5, 0.75, 0.99}) |
||||
if 4599 != ps[0] { |
||||
t.Errorf("median: 4599 != %v\n", ps[0]) |
||||
} |
||||
if 7380.5 != ps[1] { |
||||
t.Errorf("75th percentile: 7380.5 != %v\n", ps[1]) |
||||
} |
||||
if 9986.429999999998 != ps[2] { |
||||
t.Errorf("99th percentile: 9986.429999999998 != %v\n", ps[2]) |
||||
} |
||||
} |
||||
|
||||
// TestUniformSampleConcurrentUpdateCount would expose data race problems with
|
||||
// concurrent Update and Count calls on Sample when test is called with -race
|
||||
// argument
|
||||
func TestUniformSampleConcurrentUpdateCount(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping in short mode") |
||||
} |
||||
s := NewUniformSample(100) |
||||
for i := 0; i < 100; i++ { |
||||
s.Update(int64(i)) |
||||
} |
||||
quit := make(chan struct{}) |
||||
go func() { |
||||
t := time.NewTicker(10 * time.Millisecond) |
||||
for { |
||||
select { |
||||
case <-t.C: |
||||
s.Update(rand.Int63()) |
||||
case <-quit: |
||||
t.Stop() |
||||
return |
||||
} |
||||
} |
||||
}() |
||||
for i := 0; i < 1000; i++ { |
||||
s.Count() |
||||
time.Sleep(5 * time.Millisecond) |
||||
} |
||||
quit <- struct{}{} |
||||
} |
@ -0,0 +1,69 @@ |
||||
// Metrics output to StatHat.
|
||||
package stathat |
||||
|
||||
import ( |
||||
"github.com/rcrowley/go-metrics" |
||||
"github.com/stathat/go" |
||||
"log" |
||||
"time" |
||||
) |
||||
|
||||
func Stathat(r metrics.Registry, d time.Duration, userkey string) { |
||||
for { |
||||
if err := sh(r, userkey); nil != err { |
||||
log.Println(err) |
||||
} |
||||
time.Sleep(d) |
||||
} |
||||
} |
||||
|
||||
func sh(r metrics.Registry, userkey string) error { |
||||
r.Each(func(name string, i interface{}) { |
||||
switch metric := i.(type) { |
||||
case metrics.Counter: |
||||
stathat.PostEZCount(name, userkey, int(metric.Count())) |
||||
case metrics.Gauge: |
||||
stathat.PostEZValue(name, userkey, float64(metric.Value())) |
||||
case metrics.GaugeFloat64: |
||||
stathat.PostEZValue(name, userkey, float64(metric.Value())) |
||||
case metrics.Histogram: |
||||
h := metric.Snapshot() |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
stathat.PostEZCount(name+".count", userkey, int(h.Count())) |
||||
stathat.PostEZValue(name+".min", userkey, float64(h.Min())) |
||||
stathat.PostEZValue(name+".max", userkey, float64(h.Max())) |
||||
stathat.PostEZValue(name+".mean", userkey, float64(h.Mean())) |
||||
stathat.PostEZValue(name+".std-dev", userkey, float64(h.StdDev())) |
||||
stathat.PostEZValue(name+".50-percentile", userkey, float64(ps[0])) |
||||
stathat.PostEZValue(name+".75-percentile", userkey, float64(ps[1])) |
||||
stathat.PostEZValue(name+".95-percentile", userkey, float64(ps[2])) |
||||
stathat.PostEZValue(name+".99-percentile", userkey, float64(ps[3])) |
||||
stathat.PostEZValue(name+".999-percentile", userkey, float64(ps[4])) |
||||
case metrics.Meter: |
||||
m := metric.Snapshot() |
||||
stathat.PostEZCount(name+".count", userkey, int(m.Count())) |
||||
stathat.PostEZValue(name+".one-minute", userkey, float64(m.Rate1())) |
||||
stathat.PostEZValue(name+".five-minute", userkey, float64(m.Rate5())) |
||||
stathat.PostEZValue(name+".fifteen-minute", userkey, float64(m.Rate15())) |
||||
stathat.PostEZValue(name+".mean", userkey, float64(m.RateMean())) |
||||
case metrics.Timer: |
||||
t := metric.Snapshot() |
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
stathat.PostEZCount(name+".count", userkey, int(t.Count())) |
||||
stathat.PostEZValue(name+".min", userkey, float64(t.Min())) |
||||
stathat.PostEZValue(name+".max", userkey, float64(t.Max())) |
||||
stathat.PostEZValue(name+".mean", userkey, float64(t.Mean())) |
||||
stathat.PostEZValue(name+".std-dev", userkey, float64(t.StdDev())) |
||||
stathat.PostEZValue(name+".50-percentile", userkey, float64(ps[0])) |
||||
stathat.PostEZValue(name+".75-percentile", userkey, float64(ps[1])) |
||||
stathat.PostEZValue(name+".95-percentile", userkey, float64(ps[2])) |
||||
stathat.PostEZValue(name+".99-percentile", userkey, float64(ps[3])) |
||||
stathat.PostEZValue(name+".999-percentile", userkey, float64(ps[4])) |
||||
stathat.PostEZValue(name+".one-minute", userkey, float64(t.Rate1())) |
||||
stathat.PostEZValue(name+".five-minute", userkey, float64(t.Rate5())) |
||||
stathat.PostEZValue(name+".fifteen-minute", userkey, float64(t.Rate15())) |
||||
stathat.PostEZValue(name+".mean-rate", userkey, float64(t.RateMean())) |
||||
} |
||||
}) |
||||
return nil |
||||
} |
@ -0,0 +1,78 @@ |
||||
// +build !windows
|
||||
|
||||
package metrics |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log/syslog" |
||||
"time" |
||||
) |
||||
|
||||
// Output each metric in the given registry to syslog periodically using
|
||||
// the given syslogger.
|
||||
func Syslog(r Registry, d time.Duration, w *syslog.Writer) { |
||||
for _ = range time.Tick(d) { |
||||
r.Each(func(name string, i interface{}) { |
||||
switch metric := i.(type) { |
||||
case Counter: |
||||
w.Info(fmt.Sprintf("counter %s: count: %d", name, metric.Count())) |
||||
case Gauge: |
||||
w.Info(fmt.Sprintf("gauge %s: value: %d", name, metric.Value())) |
||||
case GaugeFloat64: |
||||
w.Info(fmt.Sprintf("gauge %s: value: %f", name, metric.Value())) |
||||
case Healthcheck: |
||||
metric.Check() |
||||
w.Info(fmt.Sprintf("healthcheck %s: error: %v", name, metric.Error())) |
||||
case Histogram: |
||||
h := metric.Snapshot() |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
w.Info(fmt.Sprintf( |
||||
"histogram %s: count: %d min: %d max: %d mean: %.2f stddev: %.2f median: %.2f 75%%: %.2f 95%%: %.2f 99%%: %.2f 99.9%%: %.2f", |
||||
name, |
||||
h.Count(), |
||||
h.Min(), |
||||
h.Max(), |
||||
h.Mean(), |
||||
h.StdDev(), |
||||
ps[0], |
||||
ps[1], |
||||
ps[2], |
||||
ps[3], |
||||
ps[4], |
||||
)) |
||||
case Meter: |
||||
m := metric.Snapshot() |
||||
w.Info(fmt.Sprintf( |
||||
"meter %s: count: %d 1-min: %.2f 5-min: %.2f 15-min: %.2f mean: %.2f", |
||||
name, |
||||
m.Count(), |
||||
m.Rate1(), |
||||
m.Rate5(), |
||||
m.Rate15(), |
||||
m.RateMean(), |
||||
)) |
||||
case Timer: |
||||
t := metric.Snapshot() |
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
w.Info(fmt.Sprintf( |
||||
"timer %s: count: %d min: %d max: %d mean: %.2f stddev: %.2f median: %.2f 75%%: %.2f 95%%: %.2f 99%%: %.2f 99.9%%: %.2f 1-min: %.2f 5-min: %.2f 15-min: %.2f mean-rate: %.2f", |
||||
name, |
||||
t.Count(), |
||||
t.Min(), |
||||
t.Max(), |
||||
t.Mean(), |
||||
t.StdDev(), |
||||
ps[0], |
||||
ps[1], |
||||
ps[2], |
||||
ps[3], |
||||
ps[4], |
||||
t.Rate1(), |
||||
t.Rate5(), |
||||
t.Rate15(), |
||||
t.RateMean(), |
||||
)) |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,311 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// Timers capture the duration and rate of events.
|
||||
type Timer interface { |
||||
Count() int64 |
||||
Max() int64 |
||||
Mean() float64 |
||||
Min() int64 |
||||
Percentile(float64) float64 |
||||
Percentiles([]float64) []float64 |
||||
Rate1() float64 |
||||
Rate5() float64 |
||||
Rate15() float64 |
||||
RateMean() float64 |
||||
Snapshot() Timer |
||||
StdDev() float64 |
||||
Sum() int64 |
||||
Time(func()) |
||||
Update(time.Duration) |
||||
UpdateSince(time.Time) |
||||
Variance() float64 |
||||
} |
||||
|
||||
// GetOrRegisterTimer returns an existing Timer or constructs and registers a
|
||||
// new StandardTimer.
|
||||
func GetOrRegisterTimer(name string, r Registry) Timer { |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
return r.GetOrRegister(name, NewTimer).(Timer) |
||||
} |
||||
|
||||
// NewCustomTimer constructs a new StandardTimer from a Histogram and a Meter.
|
||||
func NewCustomTimer(h Histogram, m Meter) Timer { |
||||
if UseNilMetrics { |
||||
return NilTimer{} |
||||
} |
||||
return &StandardTimer{ |
||||
histogram: h, |
||||
meter: m, |
||||
} |
||||
} |
||||
|
||||
// NewRegisteredTimer constructs and registers a new StandardTimer.
|
||||
func NewRegisteredTimer(name string, r Registry) Timer { |
||||
c := NewTimer() |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
r.Register(name, c) |
||||
return c |
||||
} |
||||
|
||||
// NewTimer constructs a new StandardTimer using an exponentially-decaying
|
||||
// sample with the same reservoir size and alpha as UNIX load averages.
|
||||
func NewTimer() Timer { |
||||
if UseNilMetrics { |
||||
return NilTimer{} |
||||
} |
||||
return &StandardTimer{ |
||||
histogram: NewHistogram(NewExpDecaySample(1028, 0.015)), |
||||
meter: NewMeter(), |
||||
} |
||||
} |
||||
|
||||
// NilTimer is a no-op Timer.
|
||||
type NilTimer struct { |
||||
h Histogram |
||||
m Meter |
||||
} |
||||
|
||||
// Count is a no-op.
|
||||
func (NilTimer) Count() int64 { return 0 } |
||||
|
||||
// Max is a no-op.
|
||||
func (NilTimer) Max() int64 { return 0 } |
||||
|
||||
// Mean is a no-op.
|
||||
func (NilTimer) Mean() float64 { return 0.0 } |
||||
|
||||
// Min is a no-op.
|
||||
func (NilTimer) Min() int64 { return 0 } |
||||
|
||||
// Percentile is a no-op.
|
||||
func (NilTimer) Percentile(p float64) float64 { return 0.0 } |
||||
|
||||
// Percentiles is a no-op.
|
||||
func (NilTimer) Percentiles(ps []float64) []float64 { |
||||
return make([]float64, len(ps)) |
||||
} |
||||
|
||||
// Rate1 is a no-op.
|
||||
func (NilTimer) Rate1() float64 { return 0.0 } |
||||
|
||||
// Rate5 is a no-op.
|
||||
func (NilTimer) Rate5() float64 { return 0.0 } |
||||
|
||||
// Rate15 is a no-op.
|
||||
func (NilTimer) Rate15() float64 { return 0.0 } |
||||
|
||||
// RateMean is a no-op.
|
||||
func (NilTimer) RateMean() float64 { return 0.0 } |
||||
|
||||
// Snapshot is a no-op.
|
||||
func (NilTimer) Snapshot() Timer { return NilTimer{} } |
||||
|
||||
// StdDev is a no-op.
|
||||
func (NilTimer) StdDev() float64 { return 0.0 } |
||||
|
||||
// Sum is a no-op.
|
||||
func (NilTimer) Sum() int64 { return 0 } |
||||
|
||||
// Time is a no-op.
|
||||
func (NilTimer) Time(func()) {} |
||||
|
||||
// Update is a no-op.
|
||||
func (NilTimer) Update(time.Duration) {} |
||||
|
||||
// UpdateSince is a no-op.
|
||||
func (NilTimer) UpdateSince(time.Time) {} |
||||
|
||||
// Variance is a no-op.
|
||||
func (NilTimer) Variance() float64 { return 0.0 } |
||||
|
||||
// StandardTimer is the standard implementation of a Timer and uses a Histogram
|
||||
// and Meter.
|
||||
type StandardTimer struct { |
||||
histogram Histogram |
||||
meter Meter |
||||
mutex sync.Mutex |
||||
} |
||||
|
||||
// Count returns the number of events recorded.
|
||||
func (t *StandardTimer) Count() int64 { |
||||
return t.histogram.Count() |
||||
} |
||||
|
||||
// Max returns the maximum value in the sample.
|
||||
func (t *StandardTimer) Max() int64 { |
||||
return t.histogram.Max() |
||||
} |
||||
|
||||
// Mean returns the mean of the values in the sample.
|
||||
func (t *StandardTimer) Mean() float64 { |
||||
return t.histogram.Mean() |
||||
} |
||||
|
||||
// Min returns the minimum value in the sample.
|
||||
func (t *StandardTimer) Min() int64 { |
||||
return t.histogram.Min() |
||||
} |
||||
|
||||
// Percentile returns an arbitrary percentile of the values in the sample.
|
||||
func (t *StandardTimer) Percentile(p float64) float64 { |
||||
return t.histogram.Percentile(p) |
||||
} |
||||
|
||||
// Percentiles returns a slice of arbitrary percentiles of the values in the
|
||||
// sample.
|
||||
func (t *StandardTimer) Percentiles(ps []float64) []float64 { |
||||
return t.histogram.Percentiles(ps) |
||||
} |
||||
|
||||
// Rate1 returns the one-minute moving average rate of events per second.
|
||||
func (t *StandardTimer) Rate1() float64 { |
||||
return t.meter.Rate1() |
||||
} |
||||
|
||||
// Rate5 returns the five-minute moving average rate of events per second.
|
||||
func (t *StandardTimer) Rate5() float64 { |
||||
return t.meter.Rate5() |
||||
} |
||||
|
||||
// Rate15 returns the fifteen-minute moving average rate of events per second.
|
||||
func (t *StandardTimer) Rate15() float64 { |
||||
return t.meter.Rate15() |
||||
} |
||||
|
||||
// RateMean returns the meter's mean rate of events per second.
|
||||
func (t *StandardTimer) RateMean() float64 { |
||||
return t.meter.RateMean() |
||||
} |
||||
|
||||
// Snapshot returns a read-only copy of the timer.
|
||||
func (t *StandardTimer) Snapshot() Timer { |
||||
t.mutex.Lock() |
||||
defer t.mutex.Unlock() |
||||
return &TimerSnapshot{ |
||||
histogram: t.histogram.Snapshot().(*HistogramSnapshot), |
||||
meter: t.meter.Snapshot().(*MeterSnapshot), |
||||
} |
||||
} |
||||
|
||||
// StdDev returns the standard deviation of the values in the sample.
|
||||
func (t *StandardTimer) StdDev() float64 { |
||||
return t.histogram.StdDev() |
||||
} |
||||
|
||||
// Sum returns the sum in the sample.
|
||||
func (t *StandardTimer) Sum() int64 { |
||||
return t.histogram.Sum() |
||||
} |
||||
|
||||
// Record the duration of the execution of the given function.
|
||||
func (t *StandardTimer) Time(f func()) { |
||||
ts := time.Now() |
||||
f() |
||||
t.Update(time.Since(ts)) |
||||
} |
||||
|
||||
// Record the duration of an event.
|
||||
func (t *StandardTimer) Update(d time.Duration) { |
||||
t.mutex.Lock() |
||||
defer t.mutex.Unlock() |
||||
t.histogram.Update(int64(d)) |
||||
t.meter.Mark(1) |
||||
} |
||||
|
||||
// Record the duration of an event that started at a time and ends now.
|
||||
func (t *StandardTimer) UpdateSince(ts time.Time) { |
||||
t.mutex.Lock() |
||||
defer t.mutex.Unlock() |
||||
t.histogram.Update(int64(time.Since(ts))) |
||||
t.meter.Mark(1) |
||||
} |
||||
|
||||
// Variance returns the variance of the values in the sample.
|
||||
func (t *StandardTimer) Variance() float64 { |
||||
return t.histogram.Variance() |
||||
} |
||||
|
||||
// TimerSnapshot is a read-only copy of another Timer.
|
||||
type TimerSnapshot struct { |
||||
histogram *HistogramSnapshot |
||||
meter *MeterSnapshot |
||||
} |
||||
|
||||
// Count returns the number of events recorded at the time the snapshot was
|
||||
// taken.
|
||||
func (t *TimerSnapshot) Count() int64 { return t.histogram.Count() } |
||||
|
||||
// Max returns the maximum value at the time the snapshot was taken.
|
||||
func (t *TimerSnapshot) Max() int64 { return t.histogram.Max() } |
||||
|
||||
// Mean returns the mean value at the time the snapshot was taken.
|
||||
func (t *TimerSnapshot) Mean() float64 { return t.histogram.Mean() } |
||||
|
||||
// Min returns the minimum value at the time the snapshot was taken.
|
||||
func (t *TimerSnapshot) Min() int64 { return t.histogram.Min() } |
||||
|
||||
// Percentile returns an arbitrary percentile of sampled values at the time the
|
||||
// snapshot was taken.
|
||||
func (t *TimerSnapshot) Percentile(p float64) float64 { |
||||
return t.histogram.Percentile(p) |
||||
} |
||||
|
||||
// Percentiles returns a slice of arbitrary percentiles of sampled values at
|
||||
// the time the snapshot was taken.
|
||||
func (t *TimerSnapshot) Percentiles(ps []float64) []float64 { |
||||
return t.histogram.Percentiles(ps) |
||||
} |
||||
|
||||
// Rate1 returns the one-minute moving average rate of events per second at the
|
||||
// time the snapshot was taken.
|
||||
func (t *TimerSnapshot) Rate1() float64 { return t.meter.Rate1() } |
||||
|
||||
// Rate5 returns the five-minute moving average rate of events per second at
|
||||
// the time the snapshot was taken.
|
||||
func (t *TimerSnapshot) Rate5() float64 { return t.meter.Rate5() } |
||||
|
||||
// Rate15 returns the fifteen-minute moving average rate of events per second
|
||||
// at the time the snapshot was taken.
|
||||
func (t *TimerSnapshot) Rate15() float64 { return t.meter.Rate15() } |
||||
|
||||
// RateMean returns the meter's mean rate of events per second at the time the
|
||||
// snapshot was taken.
|
||||
func (t *TimerSnapshot) RateMean() float64 { return t.meter.RateMean() } |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (t *TimerSnapshot) Snapshot() Timer { return t } |
||||
|
||||
// StdDev returns the standard deviation of the values at the time the snapshot
|
||||
// was taken.
|
||||
func (t *TimerSnapshot) StdDev() float64 { return t.histogram.StdDev() } |
||||
|
||||
// Sum returns the sum at the time the snapshot was taken.
|
||||
func (t *TimerSnapshot) Sum() int64 { return t.histogram.Sum() } |
||||
|
||||
// Time panics.
|
||||
func (*TimerSnapshot) Time(func()) { |
||||
panic("Time called on a TimerSnapshot") |
||||
} |
||||
|
||||
// Update panics.
|
||||
func (*TimerSnapshot) Update(time.Duration) { |
||||
panic("Update called on a TimerSnapshot") |
||||
} |
||||
|
||||
// UpdateSince panics.
|
||||
func (*TimerSnapshot) UpdateSince(time.Time) { |
||||
panic("UpdateSince called on a TimerSnapshot") |
||||
} |
||||
|
||||
// Variance returns the variance of the values at the time the snapshot was
|
||||
// taken.
|
||||
func (t *TimerSnapshot) Variance() float64 { return t.histogram.Variance() } |
@ -0,0 +1,81 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"math" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func BenchmarkTimer(b *testing.B) { |
||||
tm := NewTimer() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
tm.Update(1) |
||||
} |
||||
} |
||||
|
||||
func TestGetOrRegisterTimer(t *testing.T) { |
||||
r := NewRegistry() |
||||
NewRegisteredTimer("foo", r).Update(47) |
||||
if tm := GetOrRegisterTimer("foo", r); 1 != tm.Count() { |
||||
t.Fatal(tm) |
||||
} |
||||
} |
||||
|
||||
func TestTimerExtremes(t *testing.T) { |
||||
tm := NewTimer() |
||||
tm.Update(math.MaxInt64) |
||||
tm.Update(0) |
||||
if stdDev := tm.StdDev(); 4.611686018427388e+18 != stdDev { |
||||
t.Errorf("tm.StdDev(): 4.611686018427388e+18 != %v\n", stdDev) |
||||
} |
||||
} |
||||
|
||||
func TestTimerFunc(t *testing.T) { |
||||
tm := NewTimer() |
||||
tm.Time(func() { time.Sleep(50e6) }) |
||||
if max := tm.Max(); 45e6 > max || max > 55e6 { |
||||
t.Errorf("tm.Max(): 45e6 > %v || %v > 55e6\n", max, max) |
||||
} |
||||
} |
||||
|
||||
func TestTimerZero(t *testing.T) { |
||||
tm := NewTimer() |
||||
if count := tm.Count(); 0 != count { |
||||
t.Errorf("tm.Count(): 0 != %v\n", count) |
||||
} |
||||
if min := tm.Min(); 0 != min { |
||||
t.Errorf("tm.Min(): 0 != %v\n", min) |
||||
} |
||||
if max := tm.Max(); 0 != max { |
||||
t.Errorf("tm.Max(): 0 != %v\n", max) |
||||
} |
||||
if mean := tm.Mean(); 0.0 != mean { |
||||
t.Errorf("tm.Mean(): 0.0 != %v\n", mean) |
||||
} |
||||
if stdDev := tm.StdDev(); 0.0 != stdDev { |
||||
t.Errorf("tm.StdDev(): 0.0 != %v\n", stdDev) |
||||
} |
||||
ps := tm.Percentiles([]float64{0.5, 0.75, 0.99}) |
||||
if 0.0 != ps[0] { |
||||
t.Errorf("median: 0.0 != %v\n", ps[0]) |
||||
} |
||||
if 0.0 != ps[1] { |
||||
t.Errorf("75th percentile: 0.0 != %v\n", ps[1]) |
||||
} |
||||
if 0.0 != ps[2] { |
||||
t.Errorf("99th percentile: 0.0 != %v\n", ps[2]) |
||||
} |
||||
if rate1 := tm.Rate1(); 0.0 != rate1 { |
||||
t.Errorf("tm.Rate1(): 0.0 != %v\n", rate1) |
||||
} |
||||
if rate5 := tm.Rate5(); 0.0 != rate5 { |
||||
t.Errorf("tm.Rate5(): 0.0 != %v\n", rate5) |
||||
} |
||||
if rate15 := tm.Rate15(); 0.0 != rate15 { |
||||
t.Errorf("tm.Rate15(): 0.0 != %v\n", rate15) |
||||
} |
||||
if rateMean := tm.RateMean(); 0.0 != rateMean { |
||||
t.Errorf("tm.RateMean(): 0.0 != %v\n", rateMean) |
||||
} |
||||
} |
@ -0,0 +1,100 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"sort" |
||||
"time" |
||||
) |
||||
|
||||
// Write sorts writes each metric in the given registry periodically to the
|
||||
// given io.Writer.
|
||||
func Write(r Registry, d time.Duration, w io.Writer) { |
||||
for _ = range time.Tick(d) { |
||||
WriteOnce(r, w) |
||||
} |
||||
} |
||||
|
||||
// WriteOnce sorts and writes metrics in the given registry to the given
|
||||
// io.Writer.
|
||||
func WriteOnce(r Registry, w io.Writer) { |
||||
var namedMetrics namedMetricSlice |
||||
r.Each(func(name string, i interface{}) { |
||||
namedMetrics = append(namedMetrics, namedMetric{name, i}) |
||||
}) |
||||
|
||||
sort.Sort(namedMetrics) |
||||
for _, namedMetric := range namedMetrics { |
||||
switch metric := namedMetric.m.(type) { |
||||
case Counter: |
||||
fmt.Fprintf(w, "counter %s\n", namedMetric.name) |
||||
fmt.Fprintf(w, " count: %9d\n", metric.Count()) |
||||
case Gauge: |
||||
fmt.Fprintf(w, "gauge %s\n", namedMetric.name) |
||||
fmt.Fprintf(w, " value: %9d\n", metric.Value()) |
||||
case GaugeFloat64: |
||||
fmt.Fprintf(w, "gauge %s\n", namedMetric.name) |
||||
fmt.Fprintf(w, " value: %f\n", metric.Value()) |
||||
case Healthcheck: |
||||
metric.Check() |
||||
fmt.Fprintf(w, "healthcheck %s\n", namedMetric.name) |
||||
fmt.Fprintf(w, " error: %v\n", metric.Error()) |
||||
case Histogram: |
||||
h := metric.Snapshot() |
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
fmt.Fprintf(w, "histogram %s\n", namedMetric.name) |
||||
fmt.Fprintf(w, " count: %9d\n", h.Count()) |
||||
fmt.Fprintf(w, " min: %9d\n", h.Min()) |
||||
fmt.Fprintf(w, " max: %9d\n", h.Max()) |
||||
fmt.Fprintf(w, " mean: %12.2f\n", h.Mean()) |
||||
fmt.Fprintf(w, " stddev: %12.2f\n", h.StdDev()) |
||||
fmt.Fprintf(w, " median: %12.2f\n", ps[0]) |
||||
fmt.Fprintf(w, " 75%%: %12.2f\n", ps[1]) |
||||
fmt.Fprintf(w, " 95%%: %12.2f\n", ps[2]) |
||||
fmt.Fprintf(w, " 99%%: %12.2f\n", ps[3]) |
||||
fmt.Fprintf(w, " 99.9%%: %12.2f\n", ps[4]) |
||||
case Meter: |
||||
m := metric.Snapshot() |
||||
fmt.Fprintf(w, "meter %s\n", namedMetric.name) |
||||
fmt.Fprintf(w, " count: %9d\n", m.Count()) |
||||
fmt.Fprintf(w, " 1-min rate: %12.2f\n", m.Rate1()) |
||||
fmt.Fprintf(w, " 5-min rate: %12.2f\n", m.Rate5()) |
||||
fmt.Fprintf(w, " 15-min rate: %12.2f\n", m.Rate15()) |
||||
fmt.Fprintf(w, " mean rate: %12.2f\n", m.RateMean()) |
||||
case Timer: |
||||
t := metric.Snapshot() |
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) |
||||
fmt.Fprintf(w, "timer %s\n", namedMetric.name) |
||||
fmt.Fprintf(w, " count: %9d\n", t.Count()) |
||||
fmt.Fprintf(w, " min: %9d\n", t.Min()) |
||||
fmt.Fprintf(w, " max: %9d\n", t.Max()) |
||||
fmt.Fprintf(w, " mean: %12.2f\n", t.Mean()) |
||||
fmt.Fprintf(w, " stddev: %12.2f\n", t.StdDev()) |
||||
fmt.Fprintf(w, " median: %12.2f\n", ps[0]) |
||||
fmt.Fprintf(w, " 75%%: %12.2f\n", ps[1]) |
||||
fmt.Fprintf(w, " 95%%: %12.2f\n", ps[2]) |
||||
fmt.Fprintf(w, " 99%%: %12.2f\n", ps[3]) |
||||
fmt.Fprintf(w, " 99.9%%: %12.2f\n", ps[4]) |
||||
fmt.Fprintf(w, " 1-min rate: %12.2f\n", t.Rate1()) |
||||
fmt.Fprintf(w, " 5-min rate: %12.2f\n", t.Rate5()) |
||||
fmt.Fprintf(w, " 15-min rate: %12.2f\n", t.Rate15()) |
||||
fmt.Fprintf(w, " mean rate: %12.2f\n", t.RateMean()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
type namedMetric struct { |
||||
name string |
||||
m interface{} |
||||
} |
||||
|
||||
// namedMetricSlice is a slice of namedMetrics that implements sort.Interface.
|
||||
type namedMetricSlice []namedMetric |
||||
|
||||
func (nms namedMetricSlice) Len() int { return len(nms) } |
||||
|
||||
func (nms namedMetricSlice) Swap(i, j int) { nms[i], nms[j] = nms[j], nms[i] } |
||||
|
||||
func (nms namedMetricSlice) Less(i, j int) bool { |
||||
return nms[i].name < nms[j].name |
||||
} |
@ -0,0 +1,22 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"sort" |
||||
"testing" |
||||
) |
||||
|
||||
func TestMetricsSorting(t *testing.T) { |
||||
var namedMetrics = namedMetricSlice{ |
||||
{name: "zzz"}, |
||||
{name: "bbb"}, |
||||
{name: "fff"}, |
||||
{name: "ggg"}, |
||||
} |
||||
|
||||
sort.Sort(namedMetrics) |
||||
for i, name := range []string{"bbb", "fff", "ggg", "zzz"} { |
||||
if namedMetrics[i].name != name { |
||||
t.Fail() |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue