mirror of https://github.com/ethereum/go-ethereum
metrics: pull library and introduce ResettingTimer and InfluxDB reporter (#15910)
* go-metrics: fork library and introduce ResettingTimer and InfluxDB reporter. * vendor: change nonsense/go-metrics to ethersphere/go-metrics * go-metrics: add tests. move ResettingTimer logic from reporter to type. * all, metrics: pull in metrics package in go-ethereum * metrics/test: make sure metrics are enabled for tests * metrics: apply gosimple rules * metrics/exp, internal/debug: init expvar endpoint when starting pprof server * internal/debug: tiny comment formatting fixpull/15980/merge
parent
7f74bdf8dd
commit
ae9f97221a
@ -0,0 +1 @@ |
||||
This repo has been forked from https://github.com/rcrowley/go-metrics at commit e181e09 |
@ -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,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,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,59 @@ |
||||
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) |
||||
} |
||||
} |
||||
|
||||
func TestFunctionalGaugeFloat64(t *testing.T) { |
||||
var counter float64 |
||||
fg := NewFunctionalGaugeFloat64(func() float64 { |
||||
counter++ |
||||
return counter |
||||
}) |
||||
fg.Value() |
||||
fg.Value() |
||||
if counter != 2 { |
||||
t.Error("counter != 2") |
||||
} |
||||
} |
||||
|
||||
func TestGetOrRegisterFunctionalGaugeFloat64(t *testing.T) { |
||||
r := NewRegistry() |
||||
NewRegisteredFunctionalGaugeFloat64("foo", r, func() float64 { return 47 }) |
||||
if g := GetOrRegisterGaugeFloat64("foo", r); 47 != g.Value() { |
||||
t.Fatal(g) |
||||
} |
||||
} |
@ -0,0 +1,68 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"fmt" |
||||
"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) |
||||
} |
||||
} |
||||
|
||||
func TestFunctionalGauge(t *testing.T) { |
||||
var counter int64 |
||||
fg := NewFunctionalGauge(func() int64 { |
||||
counter++ |
||||
return counter |
||||
}) |
||||
fg.Value() |
||||
fg.Value() |
||||
if counter != 2 { |
||||
t.Error("counter != 2") |
||||
} |
||||
} |
||||
|
||||
func TestGetOrRegisterFunctionalGauge(t *testing.T) { |
||||
r := NewRegistry() |
||||
NewRegisteredFunctionalGauge("foo", r, func() int64 { return 47 }) |
||||
if g := GetOrRegisterGauge("foo", r); 47 != g.Value() { |
||||
t.Fatal(g) |
||||
} |
||||
} |
||||
|
||||
func ExampleGetOrRegisterGauge() { |
||||
m := "server.bytes_sent" |
||||
g := GetOrRegisterGauge(m, nil) |
||||
g.Update(47) |
||||
fmt.Println(g.Value()) // Output: 47
|
||||
} |
@ -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,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,19 @@ |
||||
Copyright (c) 2015 Vincent Rischmann |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
@ -0,0 +1,30 @@ |
||||
go-metrics-influxdb |
||||
=================== |
||||
|
||||
This is a reporter for the [go-metrics](https://github.com/rcrowley/go-metrics) library which will post the metrics to [InfluxDB](https://influxdb.com/). |
||||
|
||||
Note |
||||
---- |
||||
|
||||
This is only compatible with InfluxDB 0.9+. |
||||
|
||||
Usage |
||||
----- |
||||
|
||||
```go |
||||
import "github.com/vrischmann/go-metrics-influxdb" |
||||
|
||||
go influxdb.InfluxDB( |
||||
metrics.DefaultRegistry, // metrics registry |
||||
time.Second * 10, // interval |
||||
"http://localhost:8086", // the InfluxDB url |
||||
"mydb", // your InfluxDB database |
||||
"myuser", // your InfluxDB user |
||||
"mypassword", // your InfluxDB password |
||||
) |
||||
``` |
||||
|
||||
License |
||||
------- |
||||
|
||||
go-metrics-influxdb is licensed under the MIT license. See the LICENSE file for details. |
@ -0,0 +1,227 @@ |
||||
package influxdb |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
uurl "net/url" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/metrics" |
||||
"github.com/influxdata/influxdb/client" |
||||
) |
||||
|
||||
type reporter struct { |
||||
reg metrics.Registry |
||||
interval time.Duration |
||||
|
||||
url uurl.URL |
||||
database string |
||||
username string |
||||
password string |
||||
namespace string |
||||
tags map[string]string |
||||
|
||||
client *client.Client |
||||
|
||||
cache map[string]int64 |
||||
} |
||||
|
||||
// InfluxDB starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval.
|
||||
func InfluxDB(r metrics.Registry, d time.Duration, url, database, username, password, namespace string) { |
||||
InfluxDBWithTags(r, d, url, database, username, password, namespace, nil) |
||||
} |
||||
|
||||
// InfluxDBWithTags starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval with the specified tags
|
||||
func InfluxDBWithTags(r metrics.Registry, d time.Duration, url, database, username, password, namespace string, tags map[string]string) { |
||||
u, err := uurl.Parse(url) |
||||
if err != nil { |
||||
log.Printf("unable to parse InfluxDB url %s. err=%v", url, err) |
||||
return |
||||
} |
||||
|
||||
rep := &reporter{ |
||||
reg: r, |
||||
interval: d, |
||||
url: *u, |
||||
database: database, |
||||
username: username, |
||||
password: password, |
||||
namespace: namespace, |
||||
tags: tags, |
||||
cache: make(map[string]int64), |
||||
} |
||||
if err := rep.makeClient(); err != nil { |
||||
log.Printf("unable to make InfluxDB client. err=%v", err) |
||||
return |
||||
} |
||||
|
||||
rep.run() |
||||
} |
||||
|
||||
func (r *reporter) makeClient() (err error) { |
||||
r.client, err = client.NewClient(client.Config{ |
||||
URL: r.url, |
||||
Username: r.username, |
||||
Password: r.password, |
||||
}) |
||||
|
||||
return |
||||
} |
||||
|
||||
func (r *reporter) run() { |
||||
intervalTicker := time.Tick(r.interval) |
||||
pingTicker := time.Tick(time.Second * 5) |
||||
|
||||
for { |
||||
select { |
||||
case <-intervalTicker: |
||||
if err := r.send(); err != nil { |
||||
log.Printf("unable to send to InfluxDB. err=%v", err) |
||||
} |
||||
case <-pingTicker: |
||||
_, _, err := r.client.Ping() |
||||
if err != nil { |
||||
log.Printf("got error while sending a ping to InfluxDB, trying to recreate client. err=%v", err) |
||||
|
||||
if err = r.makeClient(); err != nil { |
||||
log.Printf("unable to make InfluxDB client. err=%v", err) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (r *reporter) send() error { |
||||
var pts []client.Point |
||||
|
||||
r.reg.Each(func(name string, i interface{}) { |
||||
now := time.Now() |
||||
namespace := r.namespace |
||||
|
||||
switch metric := i.(type) { |
||||
case metrics.Counter: |
||||
v := metric.Count() |
||||
l := r.cache[name] |
||||
pts = append(pts, client.Point{ |
||||
Measurement: fmt.Sprintf("%s%s.count", namespace, name), |
||||
Tags: r.tags, |
||||
Fields: map[string]interface{}{ |
||||
"value": v - l, |
||||
}, |
||||
Time: now, |
||||
}) |
||||
r.cache[name] = v |
||||
case metrics.Gauge: |
||||
ms := metric.Snapshot() |
||||
pts = append(pts, client.Point{ |
||||
Measurement: fmt.Sprintf("%s%s.gauge", namespace, name), |
||||
Tags: r.tags, |
||||
Fields: map[string]interface{}{ |
||||
"value": ms.Value(), |
||||
}, |
||||
Time: now, |
||||
}) |
||||
case metrics.GaugeFloat64: |
||||
ms := metric.Snapshot() |
||||
pts = append(pts, client.Point{ |
||||
Measurement: fmt.Sprintf("%s%s.gauge", namespace, name), |
||||
Tags: r.tags, |
||||
Fields: map[string]interface{}{ |
||||
"value": ms.Value(), |
||||
}, |
||||
Time: now, |
||||
}) |
||||
case metrics.Histogram: |
||||
ms := metric.Snapshot() |
||||
ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}) |
||||
pts = append(pts, client.Point{ |
||||
Measurement: fmt.Sprintf("%s%s.histogram", namespace, name), |
||||
Tags: r.tags, |
||||
Fields: map[string]interface{}{ |
||||
"count": ms.Count(), |
||||
"max": ms.Max(), |
||||
"mean": ms.Mean(), |
||||
"min": ms.Min(), |
||||
"stddev": ms.StdDev(), |
||||
"variance": ms.Variance(), |
||||
"p50": ps[0], |
||||
"p75": ps[1], |
||||
"p95": ps[2], |
||||
"p99": ps[3], |
||||
"p999": ps[4], |
||||
"p9999": ps[5], |
||||
}, |
||||
Time: now, |
||||
}) |
||||
case metrics.Meter: |
||||
ms := metric.Snapshot() |
||||
pts = append(pts, client.Point{ |
||||
Measurement: fmt.Sprintf("%s%s.meter", namespace, name), |
||||
Tags: r.tags, |
||||
Fields: map[string]interface{}{ |
||||
"count": ms.Count(), |
||||
"m1": ms.Rate1(), |
||||
"m5": ms.Rate5(), |
||||
"m15": ms.Rate15(), |
||||
"mean": ms.RateMean(), |
||||
}, |
||||
Time: now, |
||||
}) |
||||
case metrics.Timer: |
||||
ms := metric.Snapshot() |
||||
ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}) |
||||
pts = append(pts, client.Point{ |
||||
Measurement: fmt.Sprintf("%s%s.timer", namespace, name), |
||||
Tags: r.tags, |
||||
Fields: map[string]interface{}{ |
||||
"count": ms.Count(), |
||||
"max": ms.Max(), |
||||
"mean": ms.Mean(), |
||||
"min": ms.Min(), |
||||
"stddev": ms.StdDev(), |
||||
"variance": ms.Variance(), |
||||
"p50": ps[0], |
||||
"p75": ps[1], |
||||
"p95": ps[2], |
||||
"p99": ps[3], |
||||
"p999": ps[4], |
||||
"p9999": ps[5], |
||||
"m1": ms.Rate1(), |
||||
"m5": ms.Rate5(), |
||||
"m15": ms.Rate15(), |
||||
"meanrate": ms.RateMean(), |
||||
}, |
||||
Time: now, |
||||
}) |
||||
case metrics.ResettingTimer: |
||||
t := metric.Snapshot() |
||||
|
||||
if len(t.Values()) > 0 { |
||||
ps := t.Percentiles([]float64{50, 95, 99}) |
||||
val := t.Values() |
||||
pts = append(pts, client.Point{ |
||||
Measurement: fmt.Sprintf("%s%s.span", namespace, name), |
||||
Tags: r.tags, |
||||
Fields: map[string]interface{}{ |
||||
"count": len(val), |
||||
"max": val[len(val)-1], |
||||
"mean": t.Mean(), |
||||
"min": val[0], |
||||
"p50": ps[0], |
||||
"p95": ps[1], |
||||
"p99": ps[2], |
||||
}, |
||||
Time: now, |
||||
}) |
||||
} |
||||
} |
||||
}) |
||||
|
||||
bps := client.BatchPoints{ |
||||
Points: pts, |
||||
Database: r.database, |
||||
} |
||||
|
||||
_, err := r.client.Write(bps) |
||||
return err |
||||
} |
@ -0,0 +1,5 @@ |
||||
package metrics |
||||
|
||||
func init() { |
||||
Enabled = true |
||||
} |
@ -0,0 +1,31 @@ |
||||
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) { |
||||
return json.Marshal(r.GetAll()) |
||||
} |
||||
|
||||
// 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) |
||||
} |
||||
|
||||
func (p *PrefixedRegistry) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(p.GetAll()) |
||||
} |
@ -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,235 @@ |
||||
package librato |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"math" |
||||
"regexp" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/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 |
||||
Namespace 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() { |
||||
log.Printf("WARNING: This client has been DEPRECATED! It has been moved to https://github.com/mihasya/go-metrics-librato and will be removed from rcrowley/go-metrics on August 5th 2015") |
||||
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{}) { |
||||
if self.Namespace != "" { |
||||
name = fmt.Sprintf("%s.%s", self.Namespace, name) |
||||
} |
||||
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] = m.Value() |
||||
snapshot.Gauges = append(snapshot.Gauges, measurement) |
||||
case metrics.Histogram: |
||||
if m.Count() > 0 { |
||||
gauges := make([]Measurement, 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) |
||||
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,73 @@ |
||||
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(time.Millisecond), |
||||
meters: make(map[*StandardMeter]struct{}), |
||||
} |
||||
m := newStandardMeter() |
||||
ma.meters[m] = struct{}{} |
||||
go ma.tick() |
||||
m.Mark(1) |
||||
rateMean := m.RateMean() |
||||
time.Sleep(100 * time.Millisecond) |
||||
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 TestMeterStop(t *testing.T) { |
||||
l := len(arbiter.meters) |
||||
m := NewMeter() |
||||
if len(arbiter.meters) != l+1 { |
||||
t.Errorf("arbiter.meters: %d != %d\n", l+1, len(arbiter.meters)) |
||||
} |
||||
m.Stop() |
||||
if len(arbiter.meters) != l { |
||||
t.Errorf("arbiter.meters: %d != %d\n", l, len(arbiter.meters)) |
||||
} |
||||
} |
||||
|
||||
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,124 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"fmt" |
||||
"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() |
||||
} |
||||
|
||||
func Example() { |
||||
c := NewCounter() |
||||
Register("money", c) |
||||
c.Inc(17) |
||||
|
||||
// Threadsafe registration
|
||||
t := GetOrRegisterTimer("db.get.latency", nil) |
||||
t.Time(func() {}) |
||||
t.Update(1) |
||||
|
||||
fmt.Println(c.Count()) |
||||
fmt.Println(t.Min()) |
||||
// Output: 17
|
||||
// 1
|
||||
} |
@ -0,0 +1,21 @@ |
||||
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,305 @@ |
||||
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) |
||||
} |
||||
} |
||||
|
||||
func TestRegistryUnregister(t *testing.T) { |
||||
l := len(arbiter.meters) |
||||
r := NewRegistry() |
||||
r.Register("foo", NewCounter()) |
||||
r.Register("bar", NewMeter()) |
||||
r.Register("baz", NewTimer()) |
||||
if len(arbiter.meters) != l+2 { |
||||
t.Errorf("arbiter.meters: %d != %d\n", l+2, len(arbiter.meters)) |
||||
} |
||||
r.Unregister("foo") |
||||
r.Unregister("bar") |
||||
r.Unregister("baz") |
||||
if len(arbiter.meters) != l { |
||||
t.Errorf("arbiter.meters: %d != %d\n", l+2, len(arbiter.meters)) |
||||
} |
||||
} |
||||
|
||||
func TestPrefixedChildRegistryGetOrRegister(t *testing.T) { |
||||
r := NewRegistry() |
||||
pr := NewPrefixedChildRegistry(r, "prefix.") |
||||
|
||||
_ = pr.GetOrRegister("foo", NewCounter()) |
||||
|
||||
i := 0 |
||||
r.Each(func(name string, m interface{}) { |
||||
i++ |
||||
if name != "prefix.foo" { |
||||
t.Fatal(name) |
||||
} |
||||
}) |
||||
if i != 1 { |
||||
t.Fatal(i) |
||||
} |
||||
} |
||||
|
||||
func TestPrefixedRegistryGetOrRegister(t *testing.T) { |
||||
r := NewPrefixedRegistry("prefix.") |
||||
|
||||
_ = r.GetOrRegister("foo", NewCounter()) |
||||
|
||||
i := 0 |
||||
r.Each(func(name string, m interface{}) { |
||||
i++ |
||||
if name != "prefix.foo" { |
||||
t.Fatal(name) |
||||
} |
||||
}) |
||||
if i != 1 { |
||||
t.Fatal(i) |
||||
} |
||||
} |
||||
|
||||
func TestPrefixedRegistryRegister(t *testing.T) { |
||||
r := NewPrefixedRegistry("prefix.") |
||||
err := r.Register("foo", NewCounter()) |
||||
c := NewCounter() |
||||
Register("bar", c) |
||||
if err != nil { |
||||
t.Fatal(err.Error()) |
||||
} |
||||
|
||||
i := 0 |
||||
r.Each(func(name string, m interface{}) { |
||||
i++ |
||||
if name != "prefix.foo" { |
||||
t.Fatal(name) |
||||
} |
||||
}) |
||||
if i != 1 { |
||||
t.Fatal(i) |
||||
} |
||||
} |
||||
|
||||
func TestPrefixedRegistryUnregister(t *testing.T) { |
||||
r := NewPrefixedRegistry("prefix.") |
||||
|
||||
_ = r.Register("foo", NewCounter()) |
||||
|
||||
i := 0 |
||||
r.Each(func(name string, m interface{}) { |
||||
i++ |
||||
if name != "prefix.foo" { |
||||
t.Fatal(name) |
||||
} |
||||
}) |
||||
if i != 1 { |
||||
t.Fatal(i) |
||||
} |
||||
|
||||
r.Unregister("foo") |
||||
|
||||
i = 0 |
||||
r.Each(func(name string, m interface{}) { |
||||
i++ |
||||
}) |
||||
|
||||
if i != 0 { |
||||
t.Fatal(i) |
||||
} |
||||
} |
||||
|
||||
func TestPrefixedRegistryGet(t *testing.T) { |
||||
pr := NewPrefixedRegistry("prefix.") |
||||
name := "foo" |
||||
pr.Register(name, NewCounter()) |
||||
|
||||
fooCounter := pr.Get(name) |
||||
if fooCounter == nil { |
||||
t.Fatal(name) |
||||
} |
||||
} |
||||
|
||||
func TestPrefixedChildRegistryGet(t *testing.T) { |
||||
r := NewRegistry() |
||||
pr := NewPrefixedChildRegistry(r, "prefix.") |
||||
name := "foo" |
||||
pr.Register(name, NewCounter()) |
||||
fooCounter := pr.Get(name) |
||||
if fooCounter == nil { |
||||
t.Fatal(name) |
||||
} |
||||
} |
||||
|
||||
func TestChildPrefixedRegistryRegister(t *testing.T) { |
||||
r := NewPrefixedChildRegistry(DefaultRegistry, "prefix.") |
||||
err := r.Register("foo", NewCounter()) |
||||
c := NewCounter() |
||||
Register("bar", c) |
||||
if err != nil { |
||||
t.Fatal(err.Error()) |
||||
} |
||||
|
||||
i := 0 |
||||
r.Each(func(name string, m interface{}) { |
||||
i++ |
||||
if name != "prefix.foo" { |
||||
t.Fatal(name) |
||||
} |
||||
}) |
||||
if i != 1 { |
||||
t.Fatal(i) |
||||
} |
||||
} |
||||
|
||||
func TestChildPrefixedRegistryOfChildRegister(t *testing.T) { |
||||
r := NewPrefixedChildRegistry(NewRegistry(), "prefix.") |
||||
r2 := NewPrefixedChildRegistry(r, "prefix2.") |
||||
err := r.Register("foo2", NewCounter()) |
||||
if err != nil { |
||||
t.Fatal(err.Error()) |
||||
} |
||||
err = r2.Register("baz", NewCounter()) |
||||
c := NewCounter() |
||||
Register("bars", c) |
||||
|
||||
i := 0 |
||||
r2.Each(func(name string, m interface{}) { |
||||
i++ |
||||
if name != "prefix.prefix2.baz" { |
||||
//t.Fatal(name)
|
||||
} |
||||
}) |
||||
if i != 1 { |
||||
t.Fatal(i) |
||||
} |
||||
} |
||||
|
||||
func TestWalkRegistries(t *testing.T) { |
||||
r := NewPrefixedChildRegistry(NewRegistry(), "prefix.") |
||||
r2 := NewPrefixedChildRegistry(r, "prefix2.") |
||||
err := r.Register("foo2", NewCounter()) |
||||
if err != nil { |
||||
t.Fatal(err.Error()) |
||||
} |
||||
err = r2.Register("baz", NewCounter()) |
||||
c := NewCounter() |
||||
Register("bars", c) |
||||
|
||||
_, prefix := findPrefix(r2, "") |
||||
if "prefix.prefix2." != prefix { |
||||
t.Fatal(prefix) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,237 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"math" |
||||
"sort" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// Initial slice capacity for the values stored in a ResettingTimer
|
||||
const InitialResettingTimerSliceCap = 10 |
||||
|
||||
// ResettingTimer is used for storing aggregated values for timers, which are reset on every flush interval.
|
||||
type ResettingTimer interface { |
||||
Values() []int64 |
||||
Snapshot() ResettingTimer |
||||
Percentiles([]float64) []int64 |
||||
Mean() float64 |
||||
Time(func()) |
||||
Update(time.Duration) |
||||
UpdateSince(time.Time) |
||||
} |
||||
|
||||
// GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a
|
||||
// new StandardResettingTimer.
|
||||
func GetOrRegisterResettingTimer(name string, r Registry) ResettingTimer { |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
return r.GetOrRegister(name, NewResettingTimer).(ResettingTimer) |
||||
} |
||||
|
||||
// NewRegisteredResettingTimer constructs and registers a new StandardResettingTimer.
|
||||
func NewRegisteredResettingTimer(name string, r Registry) ResettingTimer { |
||||
c := NewResettingTimer() |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
r.Register(name, c) |
||||
return c |
||||
} |
||||
|
||||
// NewResettingTimer constructs a new StandardResettingTimer
|
||||
func NewResettingTimer() ResettingTimer { |
||||
if !Enabled { |
||||
return NilResettingTimer{} |
||||
} |
||||
return &StandardResettingTimer{ |
||||
values: make([]int64, 0, InitialResettingTimerSliceCap), |
||||
} |
||||
} |
||||
|
||||
// NilResettingTimer is a no-op ResettingTimer.
|
||||
type NilResettingTimer struct { |
||||
} |
||||
|
||||
// Values is a no-op.
|
||||
func (NilResettingTimer) Values() []int64 { return nil } |
||||
|
||||
// Snapshot is a no-op.
|
||||
func (NilResettingTimer) Snapshot() ResettingTimer { return NilResettingTimer{} } |
||||
|
||||
// Time is a no-op.
|
||||
func (NilResettingTimer) Time(func()) {} |
||||
|
||||
// Update is a no-op.
|
||||
func (NilResettingTimer) Update(time.Duration) {} |
||||
|
||||
// Percentiles panics.
|
||||
func (NilResettingTimer) Percentiles([]float64) []int64 { |
||||
panic("Percentiles called on a NilResettingTimer") |
||||
} |
||||
|
||||
// Mean panics.
|
||||
func (NilResettingTimer) Mean() float64 { |
||||
panic("Mean called on a NilResettingTimer") |
||||
} |
||||
|
||||
// UpdateSince is a no-op.
|
||||
func (NilResettingTimer) UpdateSince(time.Time) {} |
||||
|
||||
// StandardResettingTimer is the standard implementation of a ResettingTimer.
|
||||
// and Meter.
|
||||
type StandardResettingTimer struct { |
||||
values []int64 |
||||
mutex sync.Mutex |
||||
} |
||||
|
||||
// Values returns a slice with all measurements.
|
||||
func (t *StandardResettingTimer) Values() []int64 { |
||||
return t.values |
||||
} |
||||
|
||||
// Snapshot resets the timer and returns a read-only copy of its contents.
|
||||
func (t *StandardResettingTimer) Snapshot() ResettingTimer { |
||||
t.mutex.Lock() |
||||
defer t.mutex.Unlock() |
||||
currentValues := t.values |
||||
t.values = make([]int64, 0, InitialResettingTimerSliceCap) |
||||
|
||||
return &ResettingTimerSnapshot{ |
||||
values: currentValues, |
||||
} |
||||
} |
||||
|
||||
// Percentiles panics.
|
||||
func (t *StandardResettingTimer) Percentiles([]float64) []int64 { |
||||
panic("Percentiles called on a StandardResettingTimer") |
||||
} |
||||
|
||||
// Mean panics.
|
||||
func (t *StandardResettingTimer) Mean() float64 { |
||||
panic("Mean called on a StandardResettingTimer") |
||||
} |
||||
|
||||
// Record the duration of the execution of the given function.
|
||||
func (t *StandardResettingTimer) Time(f func()) { |
||||
ts := time.Now() |
||||
f() |
||||
t.Update(time.Since(ts)) |
||||
} |
||||
|
||||
// Record the duration of an event.
|
||||
func (t *StandardResettingTimer) Update(d time.Duration) { |
||||
t.mutex.Lock() |
||||
defer t.mutex.Unlock() |
||||
t.values = append(t.values, int64(d)) |
||||
} |
||||
|
||||
// Record the duration of an event that started at a time and ends now.
|
||||
func (t *StandardResettingTimer) UpdateSince(ts time.Time) { |
||||
t.mutex.Lock() |
||||
defer t.mutex.Unlock() |
||||
t.values = append(t.values, int64(time.Since(ts))) |
||||
} |
||||
|
||||
// ResettingTimerSnapshot is a point-in-time copy of another ResettingTimer.
|
||||
type ResettingTimerSnapshot struct { |
||||
values []int64 |
||||
mean float64 |
||||
thresholdBoundaries []int64 |
||||
calculated bool |
||||
} |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (t *ResettingTimerSnapshot) Snapshot() ResettingTimer { return t } |
||||
|
||||
// Time panics.
|
||||
func (*ResettingTimerSnapshot) Time(func()) { |
||||
panic("Time called on a ResettingTimerSnapshot") |
||||
} |
||||
|
||||
// Update panics.
|
||||
func (*ResettingTimerSnapshot) Update(time.Duration) { |
||||
panic("Update called on a ResettingTimerSnapshot") |
||||
} |
||||
|
||||
// UpdateSince panics.
|
||||
func (*ResettingTimerSnapshot) UpdateSince(time.Time) { |
||||
panic("UpdateSince called on a ResettingTimerSnapshot") |
||||
} |
||||
|
||||
// Values returns all values from snapshot.
|
||||
func (t *ResettingTimerSnapshot) Values() []int64 { |
||||
return t.values |
||||
} |
||||
|
||||
// Percentiles returns the boundaries for the input percentiles.
|
||||
func (t *ResettingTimerSnapshot) Percentiles(percentiles []float64) []int64 { |
||||
t.calc(percentiles) |
||||
|
||||
return t.thresholdBoundaries |
||||
} |
||||
|
||||
// Mean returns the mean of the snapshotted values
|
||||
func (t *ResettingTimerSnapshot) Mean() float64 { |
||||
if !t.calculated { |
||||
t.calc([]float64{}) |
||||
} |
||||
|
||||
return t.mean |
||||
} |
||||
|
||||
func (t *ResettingTimerSnapshot) calc(percentiles []float64) { |
||||
sort.Sort(Int64Slice(t.values)) |
||||
|
||||
count := len(t.values) |
||||
if count > 0 { |
||||
min := t.values[0] |
||||
max := t.values[count-1] |
||||
|
||||
cumulativeValues := make([]int64, count) |
||||
cumulativeValues[0] = min |
||||
for i := 1; i < count; i++ { |
||||
cumulativeValues[i] = t.values[i] + cumulativeValues[i-1] |
||||
} |
||||
|
||||
t.thresholdBoundaries = make([]int64, len(percentiles)) |
||||
|
||||
thresholdBoundary := max |
||||
|
||||
for i, pct := range percentiles { |
||||
if count > 1 { |
||||
var abs float64 |
||||
if pct >= 0 { |
||||
abs = pct |
||||
} else { |
||||
abs = 100 + pct |
||||
} |
||||
// poor man's math.Round(x):
|
||||
// math.Floor(x + 0.5)
|
||||
indexOfPerc := int(math.Floor(((abs / 100.0) * float64(count)) + 0.5)) |
||||
if pct >= 0 { |
||||
indexOfPerc -= 1 // index offset=0
|
||||
} |
||||
thresholdBoundary = t.values[indexOfPerc] |
||||
} |
||||
|
||||
t.thresholdBoundaries[i] = thresholdBoundary |
||||
} |
||||
|
||||
sum := cumulativeValues[count-1] |
||||
t.mean = float64(sum) / float64(count) |
||||
} else { |
||||
t.thresholdBoundaries = make([]int64, len(percentiles)) |
||||
t.mean = 0 |
||||
} |
||||
|
||||
t.calculated = true |
||||
} |
||||
|
||||
// Int64Slice attaches the methods of sort.Interface to []int64, sorting in increasing order.
|
||||
type Int64Slice []int64 |
||||
|
||||
func (s Int64Slice) Len() int { return len(s) } |
||||
func (s Int64Slice) Less(i, j int) bool { return s[i] < s[j] } |
||||
func (s Int64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
@ -0,0 +1,106 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func TestResettingTimer(t *testing.T) { |
||||
tests := []struct { |
||||
values []int64 |
||||
start int |
||||
end int |
||||
wantP50 int64 |
||||
wantP95 int64 |
||||
wantP99 int64 |
||||
wantMean float64 |
||||
wantMin int64 |
||||
wantMax int64 |
||||
}{ |
||||
{ |
||||
values: []int64{}, |
||||
start: 1, |
||||
end: 11, |
||||
wantP50: 5, wantP95: 10, wantP99: 10, |
||||
wantMin: 1, wantMax: 10, wantMean: 5.5, |
||||
}, |
||||
{ |
||||
values: []int64{}, |
||||
start: 1, |
||||
end: 101, |
||||
wantP50: 50, wantP95: 95, wantP99: 99, |
||||
wantMin: 1, wantMax: 100, wantMean: 50.5, |
||||
}, |
||||
{ |
||||
values: []int64{1}, |
||||
start: 0, |
||||
end: 0, |
||||
wantP50: 1, wantP95: 1, wantP99: 1, |
||||
wantMin: 1, wantMax: 1, wantMean: 1, |
||||
}, |
||||
{ |
||||
values: []int64{0}, |
||||
start: 0, |
||||
end: 0, |
||||
wantP50: 0, wantP95: 0, wantP99: 0, |
||||
wantMin: 0, wantMax: 0, wantMean: 0, |
||||
}, |
||||
{ |
||||
values: []int64{}, |
||||
start: 0, |
||||
end: 0, |
||||
wantP50: 0, wantP95: 0, wantP99: 0, |
||||
wantMin: 0, wantMax: 0, wantMean: 0, |
||||
}, |
||||
{ |
||||
values: []int64{1, 10}, |
||||
start: 0, |
||||
end: 0, |
||||
wantP50: 1, wantP95: 10, wantP99: 10, |
||||
wantMin: 1, wantMax: 10, wantMean: 5.5, |
||||
}, |
||||
} |
||||
for ind, tt := range tests { |
||||
timer := NewResettingTimer() |
||||
|
||||
for i := tt.start; i < tt.end; i++ { |
||||
tt.values = append(tt.values, int64(i)) |
||||
} |
||||
|
||||
for _, v := range tt.values { |
||||
timer.Update(time.Duration(v)) |
||||
} |
||||
|
||||
snap := timer.Snapshot() |
||||
|
||||
ps := snap.Percentiles([]float64{50, 95, 99}) |
||||
|
||||
val := snap.Values() |
||||
|
||||
if len(val) > 0 { |
||||
if tt.wantMin != val[0] { |
||||
t.Fatalf("%d: min: got %d, want %d", ind, val[0], tt.wantMin) |
||||
} |
||||
|
||||
if tt.wantMax != val[len(val)-1] { |
||||
t.Fatalf("%d: max: got %d, want %d", ind, val[len(val)-1], tt.wantMax) |
||||
} |
||||
} |
||||
|
||||
if tt.wantMean != snap.Mean() { |
||||
t.Fatalf("%d: mean: got %.2f, want %.2f", ind, snap.Mean(), tt.wantMean) |
||||
} |
||||
|
||||
if tt.wantP50 != ps[0] { |
||||
t.Fatalf("%d: p50: got %d, want %d", ind, ps[0], tt.wantP50) |
||||
} |
||||
|
||||
if tt.wantP95 != ps[1] { |
||||
t.Fatalf("%d: p95: got %d, want %d", ind, ps[1], tt.wantP95) |
||||
} |
||||
|
||||
if tt.wantP99 != ps[2] { |
||||
t.Fatalf("%d: p99: got %d, want %d", ind, ps[2], tt.wantP99) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,88 @@ |
||||
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 TestRuntimeMemStatsNumThread(t *testing.T) { |
||||
r := NewRegistry() |
||||
RegisterRuntimeMemStats(r) |
||||
CaptureRuntimeMemStatsOnce(r) |
||||
|
||||
if value := runtimeMetrics.NumThread.Value(); value < 1 { |
||||
t.Fatalf("got NumThread: %d, wanted at least 1", value) |
||||
} |
||||
} |
||||
|
||||
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,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,101 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"fmt" |
||||
"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 TestTimerStop(t *testing.T) { |
||||
l := len(arbiter.meters) |
||||
tm := NewTimer() |
||||
if len(arbiter.meters) != l+1 { |
||||
t.Errorf("arbiter.meters: %d != %d\n", l+1, len(arbiter.meters)) |
||||
} |
||||
tm.Stop() |
||||
if len(arbiter.meters) != l { |
||||
t.Errorf("arbiter.meters: %d != %d\n", l, len(arbiter.meters)) |
||||
} |
||||
} |
||||
|
||||
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) |
||||
} |
||||
} |
||||
|
||||
func ExampleGetOrRegisterTimer() { |
||||
m := "account.create.latency" |
||||
t := GetOrRegisterTimer(m, nil) |
||||
t.Update(47) |
||||
fmt.Println(t.Max()) // Output: 47
|
||||
} |
@ -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() |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2013-2016 Errplane Inc. |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
||||
this software and associated documentation files (the "Software"), to deal in |
||||
the Software without restriction, including without limitation the rights to |
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
||||
the Software, and to permit persons to whom the Software is furnished to do so, |
||||
subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,62 @@ |
||||
- # List |
||||
- bootstrap 3.3.5 [MIT LICENSE](https://github.com/twbs/bootstrap/blob/master/LICENSE) |
||||
- collectd.org [ISC LICENSE](https://github.com/collectd/go-collectd/blob/master/LICENSE) |
||||
- github.com/BurntSushi/toml [MIT LICENSE](https://github.com/BurntSushi/toml/blob/master/COPYING) |
||||
- github.com/RoaringBitmap/roaring [APACHE LICENSE](https://github.com/RoaringBitmap/roaring/blob/master/LICENSE) |
||||
- github.com/beorn7/perks [MIT LICENSE](https://github.com/beorn7/perks/blob/master/LICENSE) |
||||
- github.com/bmizerany/pat [MIT LICENSE](https://github.com/bmizerany/pat#license) |
||||
- github.com/boltdb/bolt [MIT LICENSE](https://github.com/boltdb/bolt/blob/master/LICENSE) |
||||
- github.com/cespare/xxhash [MIT LICENSE](https://github.com/cespare/xxhash/blob/master/LICENSE.txt) |
||||
- github.com/clarkduvall/hyperloglog [MIT LICENSE](https://github.com/clarkduvall/hyperloglog/blob/master/LICENSE) |
||||
- github.com/davecgh/go-spew/spew [ISC LICENSE](https://github.com/davecgh/go-spew/blob/master/LICENSE) |
||||
- github.com/dgrijalva/jwt-go [MIT LICENSE](https://github.com/dgrijalva/jwt-go/blob/master/LICENSE) |
||||
- github.com/dgryski/go-bits [MIT LICENSE](https://github.com/dgryski/go-bits/blob/master/LICENSE) |
||||
- github.com/dgryski/go-bitstream [MIT LICENSE](https://github.com/dgryski/go-bitstream/blob/master/LICENSE) |
||||
- github.com/glycerine/go-unsnap-stream [MIT LICENSE](https://github.com/glycerine/go-unsnap-stream/blob/master/LICENSE) |
||||
- github.com/gogo/protobuf/proto [BSD LICENSE](https://github.com/gogo/protobuf/blob/master/LICENSE) |
||||
- github.com/golang/protobuf [BSD LICENSE](https://github.com/golang/protobuf/blob/master/LICENSE) |
||||
- github.com/golang/snappy [BSD LICENSE](https://github.com/golang/snappy/blob/master/LICENSE) |
||||
- github.com/google/go-cmp [BSD LICENSE](https://github.com/google/go-cmp/blob/master/LICENSE) |
||||
- github.com/influxdata/influxql [MIT LICENSE](https://github.com/influxdata/influxql/blob/master/LICENSE) |
||||
- github.com/influxdata/usage-client [MIT LICENSE](https://github.com/influxdata/usage-client/blob/master/LICENSE.txt) |
||||
- github.com/influxdata/yamux [MOZILLA PUBLIC LICENSE](https://github.com/influxdata/yamux/blob/master/LICENSE) |
||||
- github.com/influxdata/yarpc [MIT LICENSE](https://github.com/influxdata/yarpc/blob/master/LICENSE) |
||||
- github.com/jsternberg/zap-logfmt [MIT LICENSE](https://github.com/jsternberg/zap-logfmt/blob/master/LICENSE) |
||||
- github.com/jwilder/encoding [MIT LICENSE](https://github.com/jwilder/encoding/blob/master/LICENSE) |
||||
- github.com/mattn/go-isatty [MIT LICENSE](https://github.com/mattn/go-isatty/blob/master/LICENSE) |
||||
- github.com/matttproud/golang_protobuf_extensions [APACHE LICENSE](https://github.com/matttproud/golang_protobuf_extensions/blob/master/LICENSE) |
||||
- github.com/opentracing/opentracing-go [MIT LICENSE](https://github.com/opentracing/opentracing-go/blob/master/LICENSE) |
||||
- github.com/paulbellamy/ratecounter [MIT LICENSE](https://github.com/paulbellamy/ratecounter/blob/master/LICENSE) |
||||
- github.com/peterh/liner [MIT LICENSE](https://github.com/peterh/liner/blob/master/COPYING) |
||||
- github.com/philhofer/fwd [MIT LICENSE](https://github.com/philhofer/fwd/blob/master/LICENSE.md) |
||||
- github.com/prometheus/client_golang [MIT LICENSE](https://github.com/prometheus/client_golang/blob/master/LICENSE) |
||||
- github.com/prometheus/client_model [MIT LICENSE](https://github.com/prometheus/client_model/blob/master/LICENSE) |
||||
- github.com/prometheus/common [APACHE LICENSE](https://github.com/prometheus/common/blob/master/LICENSE) |
||||
- github.com/prometheus/procfs [APACHE LICENSE](https://github.com/prometheus/procfs/blob/master/LICENSE) |
||||
- github.com/rakyll/statik [APACHE LICENSE](https://github.com/rakyll/statik/blob/master/LICENSE) |
||||
- github.com/retailnext/hllpp [BSD LICENSE](https://github.com/retailnext/hllpp/blob/master/LICENSE) |
||||
- github.com/tinylib/msgp [MIT LICENSE](https://github.com/tinylib/msgp/blob/master/LICENSE) |
||||
- go.uber.org/atomic [MIT LICENSE](https://github.com/uber-go/atomic/blob/master/LICENSE.txt) |
||||
- go.uber.org/multierr [MIT LICENSE](https://github.com/uber-go/multierr/blob/master/LICENSE.txt) |
||||
- go.uber.org/zap [MIT LICENSE](https://github.com/uber-go/zap/blob/master/LICENSE.txt) |
||||
- golang.org/x/crypto [BSD LICENSE](https://github.com/golang/crypto/blob/master/LICENSE) |
||||
- golang.org/x/net [BSD LICENSE](https://github.com/golang/net/blob/master/LICENSE) |
||||
- golang.org/x/sys [BSD LICENSE](https://github.com/golang/sys/blob/master/LICENSE) |
||||
- golang.org/x/text [BSD LICENSE](https://github.com/golang/text/blob/master/LICENSE) |
||||
- golang.org/x/time [BSD LICENSE](https://github.com/golang/time/blob/master/LICENSE) |
||||
- jquery 2.1.4 [MIT LICENSE](https://github.com/jquery/jquery/blob/master/LICENSE.txt) |
||||
- github.com/xlab/treeprint [MIT LICENSE](https://github.com/xlab/treeprint/blob/master/LICENSE) |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,306 @@ |
||||
# InfluxDB Client |
||||
|
||||
[![GoDoc](https://godoc.org/github.com/influxdata/influxdb?status.svg)](http://godoc.org/github.com/influxdata/influxdb/client/v2) |
||||
|
||||
## Description |
||||
|
||||
**NOTE:** The Go client library now has a "v2" version, with the old version |
||||
being deprecated. The new version can be imported at |
||||
`import "github.com/influxdata/influxdb/client/v2"`. It is not backwards-compatible. |
||||
|
||||
A Go client library written and maintained by the **InfluxDB** team. |
||||
This package provides convenience functions to read and write time series data. |
||||
It uses the HTTP protocol to communicate with your **InfluxDB** cluster. |
||||
|
||||
|
||||
## Getting Started |
||||
|
||||
### Connecting To Your Database |
||||
|
||||
Connecting to an **InfluxDB** database is straightforward. You will need a host |
||||
name, a port and the cluster user credentials if applicable. The default port is |
||||
8086. You can customize these settings to your specific installation via the |
||||
**InfluxDB** configuration file. |
||||
|
||||
Though not necessary for experimentation, you may want to create a new user |
||||
and authenticate the connection to your database. |
||||
|
||||
For more information please check out the |
||||
[Admin Docs](https://docs.influxdata.com/influxdb/latest/administration/). |
||||
|
||||
For the impatient, you can create a new admin user _bubba_ by firing off the |
||||
[InfluxDB CLI](https://github.com/influxdata/influxdb/blob/master/cmd/influx/main.go). |
||||
|
||||
```shell |
||||
influx |
||||
> create user bubba with password 'bumblebeetuna' |
||||
> grant all privileges to bubba |
||||
``` |
||||
|
||||
And now for good measure set the credentials in you shell environment. |
||||
In the example below we will use $INFLUX_USER and $INFLUX_PWD |
||||
|
||||
Now with the administrivia out of the way, let's connect to our database. |
||||
|
||||
NOTE: If you've opted out of creating a user, you can omit Username and Password in |
||||
the configuration below. |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
"time" |
||||
|
||||
"github.com/influxdata/influxdb/client/v2" |
||||
) |
||||
|
||||
const ( |
||||
MyDB = "square_holes" |
||||
username = "bubba" |
||||
password = "bumblebeetuna" |
||||
) |
||||
|
||||
|
||||
func main() { |
||||
// Create a new HTTPClient |
||||
c, err := client.NewHTTPClient(client.HTTPConfig{ |
||||
Addr: "http://localhost:8086", |
||||
Username: username, |
||||
Password: password, |
||||
}) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
|
||||
// Create a new point batch |
||||
bp, err := client.NewBatchPoints(client.BatchPointsConfig{ |
||||
Database: MyDB, |
||||
Precision: "s", |
||||
}) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
|
||||
// Create a point and add to batch |
||||
tags := map[string]string{"cpu": "cpu-total"} |
||||
fields := map[string]interface{}{ |
||||
"idle": 10.1, |
||||
"system": 53.3, |
||||
"user": 46.6, |
||||
} |
||||
|
||||
pt, err := client.NewPoint("cpu_usage", tags, fields, time.Now()) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
bp.AddPoint(pt) |
||||
|
||||
// Write the batch |
||||
if err := c.Write(bp); err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
``` |
||||
|
||||
### Inserting Data |
||||
|
||||
Time series data aka *points* are written to the database using batch inserts. |
||||
The mechanism is to create one or more points and then create a batch aka |
||||
*batch points* and write these to a given database and series. A series is a |
||||
combination of a measurement (time/values) and a set of tags. |
||||
|
||||
In this sample we will create a batch of a 1,000 points. Each point has a time and |
||||
a single value as well as 2 tags indicating a shape and color. We write these points |
||||
to a database called _square_holes_ using a measurement named _shapes_. |
||||
|
||||
NOTE: You can specify a RetentionPolicy as part of the batch points. If not |
||||
provided InfluxDB will use the database _default_ retention policy. |
||||
|
||||
```go |
||||
|
||||
func writePoints(clnt client.Client) { |
||||
sampleSize := 1000 |
||||
|
||||
bp, err := client.NewBatchPoints(client.BatchPointsConfig{ |
||||
Database: "systemstats", |
||||
Precision: "us", |
||||
}) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
|
||||
rand.Seed(time.Now().UnixNano()) |
||||
for i := 0; i < sampleSize; i++ { |
||||
regions := []string{"us-west1", "us-west2", "us-west3", "us-east1"} |
||||
tags := map[string]string{ |
||||
"cpu": "cpu-total", |
||||
"host": fmt.Sprintf("host%d", rand.Intn(1000)), |
||||
"region": regions[rand.Intn(len(regions))], |
||||
} |
||||
|
||||
idle := rand.Float64() * 100.0 |
||||
fields := map[string]interface{}{ |
||||
"idle": idle, |
||||
"busy": 100.0 - idle, |
||||
} |
||||
|
||||
pt, err := client.NewPoint( |
||||
"cpu_usage", |
||||
tags, |
||||
fields, |
||||
time.Now(), |
||||
) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
bp.AddPoint(pt) |
||||
} |
||||
|
||||
if err := clnt.Write(bp); err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
||||
``` |
||||
|
||||
#### Uint64 Support |
||||
|
||||
The `uint64` data type is supported if your server is version `1.4.0` or |
||||
greater. To write a data point as an unsigned integer, you must insert |
||||
the point as `uint64`. You cannot use `uint` or any of the other |
||||
derivatives because previous versions of the client have supported |
||||
writing those types as an integer. |
||||
|
||||
### Querying Data |
||||
|
||||
One nice advantage of using **InfluxDB** the ability to query your data using familiar |
||||
SQL constructs. In this example we can create a convenience function to query the database |
||||
as follows: |
||||
|
||||
```go |
||||
// queryDB convenience function to query the database |
||||
func queryDB(clnt client.Client, cmd string) (res []client.Result, err error) { |
||||
q := client.Query{ |
||||
Command: cmd, |
||||
Database: MyDB, |
||||
} |
||||
if response, err := clnt.Query(q); err == nil { |
||||
if response.Error() != nil { |
||||
return res, response.Error() |
||||
} |
||||
res = response.Results |
||||
} else { |
||||
return res, err |
||||
} |
||||
return res, nil |
||||
} |
||||
``` |
||||
|
||||
#### Creating a Database |
||||
|
||||
```go |
||||
_, err := queryDB(clnt, fmt.Sprintf("CREATE DATABASE %s", MyDB)) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
``` |
||||
|
||||
#### Count Records |
||||
|
||||
```go |
||||
q := fmt.Sprintf("SELECT count(%s) FROM %s", "value", MyMeasurement) |
||||
res, err := queryDB(clnt, q) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
count := res[0].Series[0].Values[0][1] |
||||
log.Printf("Found a total of %v records\n", count) |
||||
``` |
||||
|
||||
#### Find the last 10 _shapes_ records |
||||
|
||||
```go |
||||
q := fmt.Sprintf("SELECT * FROM %s LIMIT %d", MyMeasurement, 10) |
||||
res, err = queryDB(clnt, q) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
|
||||
for i, row := range res[0].Series[0].Values { |
||||
t, err := time.Parse(time.RFC3339, row[0].(string)) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
val := row[1].(string) |
||||
log.Printf("[%2d] %s: %s\n", i, t.Format(time.Stamp), val) |
||||
} |
||||
``` |
||||
|
||||
### Using the UDP Client |
||||
|
||||
The **InfluxDB** client also supports writing over UDP. |
||||
|
||||
```go |
||||
func WriteUDP() { |
||||
// Make client |
||||
c, err := client.NewUDPClient("localhost:8089") |
||||
if err != nil { |
||||
panic(err.Error()) |
||||
} |
||||
|
||||
// Create a new point batch |
||||
bp, _ := client.NewBatchPoints(client.BatchPointsConfig{ |
||||
Precision: "s", |
||||
}) |
||||
|
||||
// Create a point and add to batch |
||||
tags := map[string]string{"cpu": "cpu-total"} |
||||
fields := map[string]interface{}{ |
||||
"idle": 10.1, |
||||
"system": 53.3, |
||||
"user": 46.6, |
||||
} |
||||
pt, err := client.NewPoint("cpu_usage", tags, fields, time.Now()) |
||||
if err != nil { |
||||
panic(err.Error()) |
||||
} |
||||
bp.AddPoint(pt) |
||||
|
||||
// Write the batch |
||||
c.Write(bp) |
||||
} |
||||
``` |
||||
|
||||
### Point Splitting |
||||
|
||||
The UDP client now supports splitting single points that exceed the configured |
||||
payload size. The logic for processing each point is listed here, starting with |
||||
an empty payload. |
||||
|
||||
1. If adding the point to the current (non-empty) payload would exceed the |
||||
configured size, send the current payload. Otherwise, add it to the current |
||||
payload. |
||||
1. If the point is smaller than the configured size, add it to the payload. |
||||
1. If the point has no timestamp, just try to send the entire point as a single |
||||
UDP payload, and process the next point. |
||||
1. Since the point has a timestamp, re-use the existing measurement name, |
||||
tagset, and timestamp and create multiple new points by splitting up the |
||||
fields. The per-point length will be kept close to the configured size, |
||||
staying under it if possible. This does mean that one large field, maybe a |
||||
long string, could be sent as a larger-than-configured payload. |
||||
|
||||
The above logic attempts to respect configured payload sizes, but not sacrifice |
||||
any data integrity. Points without a timestamp can't be split, as that may |
||||
cause fields to have differing timestamps when processed by the server. |
||||
|
||||
## Go Docs |
||||
|
||||
Please refer to |
||||
[http://godoc.org/github.com/influxdata/influxdb/client/v2](http://godoc.org/github.com/influxdata/influxdb/client/v2) |
||||
for documentation. |
||||
|
||||
## See Also |
||||
|
||||
You can also examine how the client library is used by the |
||||
[InfluxDB CLI](https://github.com/influxdata/influxdb/blob/master/cmd/influx/main.go). |
@ -0,0 +1,840 @@ |
||||
// Package client implements a now-deprecated client for InfluxDB;
|
||||
// use github.com/influxdata/influxdb/client/v2 instead.
|
||||
package client // import "github.com/influxdata/influxdb/client"
|
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"crypto/tls" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/influxdata/influxdb/models" |
||||
) |
||||
|
||||
const ( |
||||
// DefaultHost is the default host used to connect to an InfluxDB instance
|
||||
DefaultHost = "localhost" |
||||
|
||||
// DefaultPort is the default port used to connect to an InfluxDB instance
|
||||
DefaultPort = 8086 |
||||
|
||||
// DefaultTimeout is the default connection timeout used to connect to an InfluxDB instance
|
||||
DefaultTimeout = 0 |
||||
) |
||||
|
||||
// Query is used to send a command to the server. Both Command and Database are required.
|
||||
type Query struct { |
||||
Command string |
||||
Database string |
||||
|
||||
// Chunked tells the server to send back chunked responses. This places
|
||||
// less load on the server by sending back chunks of the response rather
|
||||
// than waiting for the entire response all at once.
|
||||
Chunked bool |
||||
|
||||
// ChunkSize sets the maximum number of rows that will be returned per
|
||||
// chunk. Chunks are either divided based on their series or if they hit
|
||||
// the chunk size limit.
|
||||
//
|
||||
// Chunked must be set to true for this option to be used.
|
||||
ChunkSize int |
||||
} |
||||
|
||||
// ParseConnectionString will parse a string to create a valid connection URL
|
||||
func ParseConnectionString(path string, ssl bool) (url.URL, error) { |
||||
var host string |
||||
var port int |
||||
|
||||
h, p, err := net.SplitHostPort(path) |
||||
if err != nil { |
||||
if path == "" { |
||||
host = DefaultHost |
||||
} else { |
||||
host = path |
||||
} |
||||
// If they didn't specify a port, always use the default port
|
||||
port = DefaultPort |
||||
} else { |
||||
host = h |
||||
port, err = strconv.Atoi(p) |
||||
if err != nil { |
||||
return url.URL{}, fmt.Errorf("invalid port number %q: %s\n", path, err) |
||||
} |
||||
} |
||||
|
||||
u := url.URL{ |
||||
Scheme: "http", |
||||
} |
||||
if ssl { |
||||
u.Scheme = "https" |
||||
} |
||||
|
||||
u.Host = net.JoinHostPort(host, strconv.Itoa(port)) |
||||
|
||||
return u, nil |
||||
} |
||||
|
||||
// Config is used to specify what server to connect to.
|
||||
// URL: The URL of the server connecting to.
|
||||
// Username/Password are optional. They will be passed via basic auth if provided.
|
||||
// UserAgent: If not provided, will default "InfluxDBClient",
|
||||
// Timeout: If not provided, will default to 0 (no timeout)
|
||||
type Config struct { |
||||
URL url.URL |
||||
UnixSocket string |
||||
Username string |
||||
Password string |
||||
UserAgent string |
||||
Timeout time.Duration |
||||
Precision string |
||||
WriteConsistency string |
||||
UnsafeSsl bool |
||||
} |
||||
|
||||
// NewConfig will create a config to be used in connecting to the client
|
||||
func NewConfig() Config { |
||||
return Config{ |
||||
Timeout: DefaultTimeout, |
||||
} |
||||
} |
||||
|
||||
// Client is used to make calls to the server.
|
||||
type Client struct { |
||||
url url.URL |
||||
unixSocket string |
||||
username string |
||||
password string |
||||
httpClient *http.Client |
||||
userAgent string |
||||
precision string |
||||
} |
||||
|
||||
const ( |
||||
// ConsistencyOne requires at least one data node acknowledged a write.
|
||||
ConsistencyOne = "one" |
||||
|
||||
// ConsistencyAll requires all data nodes to acknowledge a write.
|
||||
ConsistencyAll = "all" |
||||
|
||||
// ConsistencyQuorum requires a quorum of data nodes to acknowledge a write.
|
||||
ConsistencyQuorum = "quorum" |
||||
|
||||
// ConsistencyAny allows for hinted hand off, potentially no write happened yet.
|
||||
ConsistencyAny = "any" |
||||
) |
||||
|
||||
// NewClient will instantiate and return a connected client to issue commands to the server.
|
||||
func NewClient(c Config) (*Client, error) { |
||||
tlsConfig := &tls.Config{ |
||||
InsecureSkipVerify: c.UnsafeSsl, |
||||
} |
||||
|
||||
tr := &http.Transport{ |
||||
TLSClientConfig: tlsConfig, |
||||
} |
||||
|
||||
if c.UnixSocket != "" { |
||||
// No need for compression in local communications.
|
||||
tr.DisableCompression = true |
||||
|
||||
tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { |
||||
return net.Dial("unix", c.UnixSocket) |
||||
} |
||||
} |
||||
|
||||
client := Client{ |
||||
url: c.URL, |
||||
unixSocket: c.UnixSocket, |
||||
username: c.Username, |
||||
password: c.Password, |
||||
httpClient: &http.Client{Timeout: c.Timeout, Transport: tr}, |
||||
userAgent: c.UserAgent, |
||||
precision: c.Precision, |
||||
} |
||||
if client.userAgent == "" { |
||||
client.userAgent = "InfluxDBClient" |
||||
} |
||||
return &client, nil |
||||
} |
||||
|
||||
// SetAuth will update the username and passwords
|
||||
func (c *Client) SetAuth(u, p string) { |
||||
c.username = u |
||||
c.password = p |
||||
} |
||||
|
||||
// SetPrecision will update the precision
|
||||
func (c *Client) SetPrecision(precision string) { |
||||
c.precision = precision |
||||
} |
||||
|
||||
// Query sends a command to the server and returns the Response
|
||||
func (c *Client) Query(q Query) (*Response, error) { |
||||
return c.QueryContext(context.Background(), q) |
||||
} |
||||
|
||||
// QueryContext sends a command to the server and returns the Response
|
||||
// It uses a context that can be cancelled by the command line client
|
||||
func (c *Client) QueryContext(ctx context.Context, q Query) (*Response, error) { |
||||
u := c.url |
||||
|
||||
u.Path = "query" |
||||
values := u.Query() |
||||
values.Set("q", q.Command) |
||||
values.Set("db", q.Database) |
||||
if q.Chunked { |
||||
values.Set("chunked", "true") |
||||
if q.ChunkSize > 0 { |
||||
values.Set("chunk_size", strconv.Itoa(q.ChunkSize)) |
||||
} |
||||
} |
||||
if c.precision != "" { |
||||
values.Set("epoch", c.precision) |
||||
} |
||||
u.RawQuery = values.Encode() |
||||
|
||||
req, err := http.NewRequest("POST", u.String(), nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
req.Header.Set("User-Agent", c.userAgent) |
||||
if c.username != "" { |
||||
req.SetBasicAuth(c.username, c.password) |
||||
} |
||||
|
||||
req = req.WithContext(ctx) |
||||
|
||||
resp, err := c.httpClient.Do(req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
var response Response |
||||
if q.Chunked { |
||||
cr := NewChunkedResponse(resp.Body) |
||||
for { |
||||
r, err := cr.NextResponse() |
||||
if err != nil { |
||||
// If we got an error while decoding the response, send that back.
|
||||
return nil, err |
||||
} |
||||
|
||||
if r == nil { |
||||
break |
||||
} |
||||
|
||||
response.Results = append(response.Results, r.Results...) |
||||
if r.Err != nil { |
||||
response.Err = r.Err |
||||
break |
||||
} |
||||
} |
||||
} else { |
||||
dec := json.NewDecoder(resp.Body) |
||||
dec.UseNumber() |
||||
if err := dec.Decode(&response); err != nil { |
||||
// Ignore EOF errors if we got an invalid status code.
|
||||
if !(err == io.EOF && resp.StatusCode != http.StatusOK) { |
||||
return nil, err |
||||
} |
||||
} |
||||
} |
||||
|
||||
// If we don't have an error in our json response, and didn't get StatusOK,
|
||||
// then send back an error.
|
||||
if resp.StatusCode != http.StatusOK && response.Error() == nil { |
||||
return &response, fmt.Errorf("received status code %d from server", resp.StatusCode) |
||||
} |
||||
return &response, nil |
||||
} |
||||
|
||||
// Write takes BatchPoints and allows for writing of multiple points with defaults
|
||||
// If successful, error is nil and Response is nil
|
||||
// If an error occurs, Response may contain additional information if populated.
|
||||
func (c *Client) Write(bp BatchPoints) (*Response, error) { |
||||
u := c.url |
||||
u.Path = "write" |
||||
|
||||
var b bytes.Buffer |
||||
for _, p := range bp.Points { |
||||
err := checkPointTypes(p) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if p.Raw != "" { |
||||
if _, err := b.WriteString(p.Raw); err != nil { |
||||
return nil, err |
||||
} |
||||
} else { |
||||
for k, v := range bp.Tags { |
||||
if p.Tags == nil { |
||||
p.Tags = make(map[string]string, len(bp.Tags)) |
||||
} |
||||
p.Tags[k] = v |
||||
} |
||||
|
||||
if _, err := b.WriteString(p.MarshalString()); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
if err := b.WriteByte('\n'); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
req, err := http.NewRequest("POST", u.String(), &b) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
req.Header.Set("Content-Type", "") |
||||
req.Header.Set("User-Agent", c.userAgent) |
||||
if c.username != "" { |
||||
req.SetBasicAuth(c.username, c.password) |
||||
} |
||||
|
||||
precision := bp.Precision |
||||
if precision == "" { |
||||
precision = "ns" |
||||
} |
||||
|
||||
params := req.URL.Query() |
||||
params.Set("db", bp.Database) |
||||
params.Set("rp", bp.RetentionPolicy) |
||||
params.Set("precision", precision) |
||||
params.Set("consistency", bp.WriteConsistency) |
||||
req.URL.RawQuery = params.Encode() |
||||
|
||||
resp, err := c.httpClient.Do(req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
var response Response |
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK { |
||||
var err = fmt.Errorf(string(body)) |
||||
response.Err = err |
||||
return &response, err |
||||
} |
||||
|
||||
return nil, nil |
||||
} |
||||
|
||||
// WriteLineProtocol takes a string with line returns to delimit each write
|
||||
// If successful, error is nil and Response is nil
|
||||
// If an error occurs, Response may contain additional information if populated.
|
||||
func (c *Client) WriteLineProtocol(data, database, retentionPolicy, precision, writeConsistency string) (*Response, error) { |
||||
u := c.url |
||||
u.Path = "write" |
||||
|
||||
r := strings.NewReader(data) |
||||
|
||||
req, err := http.NewRequest("POST", u.String(), r) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
req.Header.Set("Content-Type", "") |
||||
req.Header.Set("User-Agent", c.userAgent) |
||||
if c.username != "" { |
||||
req.SetBasicAuth(c.username, c.password) |
||||
} |
||||
params := req.URL.Query() |
||||
params.Set("db", database) |
||||
params.Set("rp", retentionPolicy) |
||||
params.Set("precision", precision) |
||||
params.Set("consistency", writeConsistency) |
||||
req.URL.RawQuery = params.Encode() |
||||
|
||||
resp, err := c.httpClient.Do(req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
var response Response |
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK { |
||||
err := fmt.Errorf(string(body)) |
||||
response.Err = err |
||||
return &response, err |
||||
} |
||||
|
||||
return nil, nil |
||||
} |
||||
|
||||
// Ping will check to see if the server is up
|
||||
// Ping returns how long the request took, the version of the server it connected to, and an error if one occurred.
|
||||
func (c *Client) Ping() (time.Duration, string, error) { |
||||
now := time.Now() |
||||
u := c.url |
||||
u.Path = "ping" |
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil) |
||||
if err != nil { |
||||
return 0, "", err |
||||
} |
||||
req.Header.Set("User-Agent", c.userAgent) |
||||
if c.username != "" { |
||||
req.SetBasicAuth(c.username, c.password) |
||||
} |
||||
|
||||
resp, err := c.httpClient.Do(req) |
||||
if err != nil { |
||||
return 0, "", err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
version := resp.Header.Get("X-Influxdb-Version") |
||||
return time.Since(now), version, nil |
||||
} |
||||
|
||||
// Structs
|
||||
|
||||
// Message represents a user message.
|
||||
type Message struct { |
||||
Level string `json:"level,omitempty"` |
||||
Text string `json:"text,omitempty"` |
||||
} |
||||
|
||||
// Result represents a resultset returned from a single statement.
|
||||
type Result struct { |
||||
Series []models.Row |
||||
Messages []*Message |
||||
Err error |
||||
} |
||||
|
||||
// MarshalJSON encodes the result into JSON.
|
||||
func (r *Result) MarshalJSON() ([]byte, error) { |
||||
// Define a struct that outputs "error" as a string.
|
||||
var o struct { |
||||
Series []models.Row `json:"series,omitempty"` |
||||
Messages []*Message `json:"messages,omitempty"` |
||||
Err string `json:"error,omitempty"` |
||||
} |
||||
|
||||
// Copy fields to output struct.
|
||||
o.Series = r.Series |
||||
o.Messages = r.Messages |
||||
if r.Err != nil { |
||||
o.Err = r.Err.Error() |
||||
} |
||||
|
||||
return json.Marshal(&o) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes the data into the Result struct
|
||||
func (r *Result) UnmarshalJSON(b []byte) error { |
||||
var o struct { |
||||
Series []models.Row `json:"series,omitempty"` |
||||
Messages []*Message `json:"messages,omitempty"` |
||||
Err string `json:"error,omitempty"` |
||||
} |
||||
|
||||
dec := json.NewDecoder(bytes.NewBuffer(b)) |
||||
dec.UseNumber() |
||||
err := dec.Decode(&o) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
r.Series = o.Series |
||||
r.Messages = o.Messages |
||||
if o.Err != "" { |
||||
r.Err = errors.New(o.Err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Response represents a list of statement results.
|
||||
type Response struct { |
||||
Results []Result |
||||
Err error |
||||
} |
||||
|
||||
// MarshalJSON encodes the response into JSON.
|
||||
func (r *Response) MarshalJSON() ([]byte, error) { |
||||
// Define a struct that outputs "error" as a string.
|
||||
var o struct { |
||||
Results []Result `json:"results,omitempty"` |
||||
Err string `json:"error,omitempty"` |
||||
} |
||||
|
||||
// Copy fields to output struct.
|
||||
o.Results = r.Results |
||||
if r.Err != nil { |
||||
o.Err = r.Err.Error() |
||||
} |
||||
|
||||
return json.Marshal(&o) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes the data into the Response struct
|
||||
func (r *Response) UnmarshalJSON(b []byte) error { |
||||
var o struct { |
||||
Results []Result `json:"results,omitempty"` |
||||
Err string `json:"error,omitempty"` |
||||
} |
||||
|
||||
dec := json.NewDecoder(bytes.NewBuffer(b)) |
||||
dec.UseNumber() |
||||
err := dec.Decode(&o) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
r.Results = o.Results |
||||
if o.Err != "" { |
||||
r.Err = errors.New(o.Err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Error returns the first error from any statement.
|
||||
// Returns nil if no errors occurred on any statements.
|
||||
func (r *Response) Error() error { |
||||
if r.Err != nil { |
||||
return r.Err |
||||
} |
||||
for _, result := range r.Results { |
||||
if result.Err != nil { |
||||
return result.Err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// duplexReader reads responses and writes it to another writer while
|
||||
// satisfying the reader interface.
|
||||
type duplexReader struct { |
||||
r io.Reader |
||||
w io.Writer |
||||
} |
||||
|
||||
func (r *duplexReader) Read(p []byte) (n int, err error) { |
||||
n, err = r.r.Read(p) |
||||
if err == nil { |
||||
r.w.Write(p[:n]) |
||||
} |
||||
return n, err |
||||
} |
||||
|
||||
// ChunkedResponse represents a response from the server that
|
||||
// uses chunking to stream the output.
|
||||
type ChunkedResponse struct { |
||||
dec *json.Decoder |
||||
duplex *duplexReader |
||||
buf bytes.Buffer |
||||
} |
||||
|
||||
// NewChunkedResponse reads a stream and produces responses from the stream.
|
||||
func NewChunkedResponse(r io.Reader) *ChunkedResponse { |
||||
resp := &ChunkedResponse{} |
||||
resp.duplex = &duplexReader{r: r, w: &resp.buf} |
||||
resp.dec = json.NewDecoder(resp.duplex) |
||||
resp.dec.UseNumber() |
||||
return resp |
||||
} |
||||
|
||||
// NextResponse reads the next line of the stream and returns a response.
|
||||
func (r *ChunkedResponse) NextResponse() (*Response, error) { |
||||
var response Response |
||||
if err := r.dec.Decode(&response); err != nil { |
||||
if err == io.EOF { |
||||
return nil, nil |
||||
} |
||||
// A decoding error happened. This probably means the server crashed
|
||||
// and sent a last-ditch error message to us. Ensure we have read the
|
||||
// entirety of the connection to get any remaining error text.
|
||||
io.Copy(ioutil.Discard, r.duplex) |
||||
return nil, errors.New(strings.TrimSpace(r.buf.String())) |
||||
} |
||||
r.buf.Reset() |
||||
return &response, nil |
||||
} |
||||
|
||||
// Point defines the fields that will be written to the database
|
||||
// Measurement, Time, and Fields are required
|
||||
// Precision can be specified if the time is in epoch format (integer).
|
||||
// Valid values for Precision are n, u, ms, s, m, and h
|
||||
type Point struct { |
||||
Measurement string |
||||
Tags map[string]string |
||||
Time time.Time |
||||
Fields map[string]interface{} |
||||
Precision string |
||||
Raw string |
||||
} |
||||
|
||||
// MarshalJSON will format the time in RFC3339Nano
|
||||
// Precision is also ignored as it is only used for writing, not reading
|
||||
// Or another way to say it is we always send back in nanosecond precision
|
||||
func (p *Point) MarshalJSON() ([]byte, error) { |
||||
point := struct { |
||||
Measurement string `json:"measurement,omitempty"` |
||||
Tags map[string]string `json:"tags,omitempty"` |
||||
Time string `json:"time,omitempty"` |
||||
Fields map[string]interface{} `json:"fields,omitempty"` |
||||
Precision string `json:"precision,omitempty"` |
||||
}{ |
||||
Measurement: p.Measurement, |
||||
Tags: p.Tags, |
||||
Fields: p.Fields, |
||||
Precision: p.Precision, |
||||
} |
||||
// Let it omit empty if it's really zero
|
||||
if !p.Time.IsZero() { |
||||
point.Time = p.Time.UTC().Format(time.RFC3339Nano) |
||||
} |
||||
return json.Marshal(&point) |
||||
} |
||||
|
||||
// MarshalString renders string representation of a Point with specified
|
||||
// precision. The default precision is nanoseconds.
|
||||
func (p *Point) MarshalString() string { |
||||
pt, err := models.NewPoint(p.Measurement, models.NewTags(p.Tags), p.Fields, p.Time) |
||||
if err != nil { |
||||
return "# ERROR: " + err.Error() + " " + p.Measurement |
||||
} |
||||
if p.Precision == "" || p.Precision == "ns" || p.Precision == "n" { |
||||
return pt.String() |
||||
} |
||||
return pt.PrecisionString(p.Precision) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes the data into the Point struct
|
||||
func (p *Point) UnmarshalJSON(b []byte) error { |
||||
var normal struct { |
||||
Measurement string `json:"measurement"` |
||||
Tags map[string]string `json:"tags"` |
||||
Time time.Time `json:"time"` |
||||
Precision string `json:"precision"` |
||||
Fields map[string]interface{} `json:"fields"` |
||||
} |
||||
var epoch struct { |
||||
Measurement string `json:"measurement"` |
||||
Tags map[string]string `json:"tags"` |
||||
Time *int64 `json:"time"` |
||||
Precision string `json:"precision"` |
||||
Fields map[string]interface{} `json:"fields"` |
||||
} |
||||
|
||||
if err := func() error { |
||||
var err error |
||||
dec := json.NewDecoder(bytes.NewBuffer(b)) |
||||
dec.UseNumber() |
||||
if err = dec.Decode(&epoch); err != nil { |
||||
return err |
||||
} |
||||
// Convert from epoch to time.Time, but only if Time
|
||||
// was actually set.
|
||||
var ts time.Time |
||||
if epoch.Time != nil { |
||||
ts, err = EpochToTime(*epoch.Time, epoch.Precision) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
p.Measurement = epoch.Measurement |
||||
p.Tags = epoch.Tags |
||||
p.Time = ts |
||||
p.Precision = epoch.Precision |
||||
p.Fields = normalizeFields(epoch.Fields) |
||||
return nil |
||||
}(); err == nil { |
||||
return nil |
||||
} |
||||
|
||||
dec := json.NewDecoder(bytes.NewBuffer(b)) |
||||
dec.UseNumber() |
||||
if err := dec.Decode(&normal); err != nil { |
||||
return err |
||||
} |
||||
normal.Time = SetPrecision(normal.Time, normal.Precision) |
||||
p.Measurement = normal.Measurement |
||||
p.Tags = normal.Tags |
||||
p.Time = normal.Time |
||||
p.Precision = normal.Precision |
||||
p.Fields = normalizeFields(normal.Fields) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Remove any notion of json.Number
|
||||
func normalizeFields(fields map[string]interface{}) map[string]interface{} { |
||||
newFields := map[string]interface{}{} |
||||
|
||||
for k, v := range fields { |
||||
switch v := v.(type) { |
||||
case json.Number: |
||||
jv, e := v.Float64() |
||||
if e != nil { |
||||
panic(fmt.Sprintf("unable to convert json.Number to float64: %s", e)) |
||||
} |
||||
newFields[k] = jv |
||||
default: |
||||
newFields[k] = v |
||||
} |
||||
} |
||||
return newFields |
||||
} |
||||
|
||||
// BatchPoints is used to send batched data in a single write.
|
||||
// Database and Points are required
|
||||
// If no retention policy is specified, it will use the databases default retention policy.
|
||||
// If tags are specified, they will be "merged" with all points. If a point already has that tag, it will be ignored.
|
||||
// If time is specified, it will be applied to any point with an empty time.
|
||||
// Precision can be specified if the time is in epoch format (integer).
|
||||
// Valid values for Precision are n, u, ms, s, m, and h
|
||||
type BatchPoints struct { |
||||
Points []Point `json:"points,omitempty"` |
||||
Database string `json:"database,omitempty"` |
||||
RetentionPolicy string `json:"retentionPolicy,omitempty"` |
||||
Tags map[string]string `json:"tags,omitempty"` |
||||
Time time.Time `json:"time,omitempty"` |
||||
Precision string `json:"precision,omitempty"` |
||||
WriteConsistency string `json:"-"` |
||||
} |
||||
|
||||
// UnmarshalJSON decodes the data into the BatchPoints struct
|
||||
func (bp *BatchPoints) UnmarshalJSON(b []byte) error { |
||||
var normal struct { |
||||
Points []Point `json:"points"` |
||||
Database string `json:"database"` |
||||
RetentionPolicy string `json:"retentionPolicy"` |
||||
Tags map[string]string `json:"tags"` |
||||
Time time.Time `json:"time"` |
||||
Precision string `json:"precision"` |
||||
} |
||||
var epoch struct { |
||||
Points []Point `json:"points"` |
||||
Database string `json:"database"` |
||||
RetentionPolicy string `json:"retentionPolicy"` |
||||
Tags map[string]string `json:"tags"` |
||||
Time *int64 `json:"time"` |
||||
Precision string `json:"precision"` |
||||
} |
||||
|
||||
if err := func() error { |
||||
var err error |
||||
if err = json.Unmarshal(b, &epoch); err != nil { |
||||
return err |
||||
} |
||||
// Convert from epoch to time.Time
|
||||
var ts time.Time |
||||
if epoch.Time != nil { |
||||
ts, err = EpochToTime(*epoch.Time, epoch.Precision) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
bp.Points = epoch.Points |
||||
bp.Database = epoch.Database |
||||
bp.RetentionPolicy = epoch.RetentionPolicy |
||||
bp.Tags = epoch.Tags |
||||
bp.Time = ts |
||||
bp.Precision = epoch.Precision |
||||
return nil |
||||
}(); err == nil { |
||||
return nil |
||||
} |
||||
|
||||
if err := json.Unmarshal(b, &normal); err != nil { |
||||
return err |
||||
} |
||||
normal.Time = SetPrecision(normal.Time, normal.Precision) |
||||
bp.Points = normal.Points |
||||
bp.Database = normal.Database |
||||
bp.RetentionPolicy = normal.RetentionPolicy |
||||
bp.Tags = normal.Tags |
||||
bp.Time = normal.Time |
||||
bp.Precision = normal.Precision |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// utility functions
|
||||
|
||||
// Addr provides the current url as a string of the server the client is connected to.
|
||||
func (c *Client) Addr() string { |
||||
if c.unixSocket != "" { |
||||
return c.unixSocket |
||||
} |
||||
return c.url.String() |
||||
} |
||||
|
||||
// checkPointTypes ensures no unsupported types are submitted to influxdb, returning error if they are found.
|
||||
func checkPointTypes(p Point) error { |
||||
for _, v := range p.Fields { |
||||
switch v.(type) { |
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool, string, nil: |
||||
return nil |
||||
default: |
||||
return fmt.Errorf("unsupported point type: %T", v) |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// helper functions
|
||||
|
||||
// EpochToTime takes a unix epoch time and uses precision to return back a time.Time
|
||||
func EpochToTime(epoch int64, precision string) (time.Time, error) { |
||||
if precision == "" { |
||||
precision = "s" |
||||
} |
||||
var t time.Time |
||||
switch precision { |
||||
case "h": |
||||
t = time.Unix(0, epoch*int64(time.Hour)) |
||||
case "m": |
||||
t = time.Unix(0, epoch*int64(time.Minute)) |
||||
case "s": |
||||
t = time.Unix(0, epoch*int64(time.Second)) |
||||
case "ms": |
||||
t = time.Unix(0, epoch*int64(time.Millisecond)) |
||||
case "u": |
||||
t = time.Unix(0, epoch*int64(time.Microsecond)) |
||||
case "n": |
||||
t = time.Unix(0, epoch) |
||||
default: |
||||
return time.Time{}, fmt.Errorf("Unknown precision %q", precision) |
||||
} |
||||
return t, nil |
||||
} |
||||
|
||||
// SetPrecision will round a time to the specified precision
|
||||
func SetPrecision(t time.Time, precision string) time.Time { |
||||
switch precision { |
||||
case "n": |
||||
case "u": |
||||
return t.Round(time.Microsecond) |
||||
case "ms": |
||||
return t.Round(time.Millisecond) |
||||
case "s": |
||||
return t.Round(time.Second) |
||||
case "m": |
||||
return t.Round(time.Minute) |
||||
case "h": |
||||
return t.Round(time.Hour) |
||||
} |
||||
return t |
||||
} |
@ -0,0 +1,635 @@ |
||||
// Package client (v2) is the current official Go client for InfluxDB.
|
||||
package client // import "github.com/influxdata/influxdb/client/v2"
|
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/tls" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"mime" |
||||
"net/http" |
||||
"net/url" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/influxdata/influxdb/models" |
||||
) |
||||
|
||||
// HTTPConfig is the config data needed to create an HTTP Client.
|
||||
type HTTPConfig struct { |
||||
// Addr should be of the form "http://host:port"
|
||||
// or "http://[ipv6-host%zone]:port".
|
||||
Addr string |
||||
|
||||
// Username is the influxdb username, optional.
|
||||
Username string |
||||
|
||||
// Password is the influxdb password, optional.
|
||||
Password string |
||||
|
||||
// UserAgent is the http User Agent, defaults to "InfluxDBClient".
|
||||
UserAgent string |
||||
|
||||
// Timeout for influxdb writes, defaults to no timeout.
|
||||
Timeout time.Duration |
||||
|
||||
// InsecureSkipVerify gets passed to the http client, if true, it will
|
||||
// skip https certificate verification. Defaults to false.
|
||||
InsecureSkipVerify bool |
||||
|
||||
// TLSConfig allows the user to set their own TLS config for the HTTP
|
||||
// Client. If set, this option overrides InsecureSkipVerify.
|
||||
TLSConfig *tls.Config |
||||
} |
||||
|
||||
// BatchPointsConfig is the config data needed to create an instance of the BatchPoints struct.
|
||||
type BatchPointsConfig struct { |
||||
// Precision is the write precision of the points, defaults to "ns".
|
||||
Precision string |
||||
|
||||
// Database is the database to write points to.
|
||||
Database string |
||||
|
||||
// RetentionPolicy is the retention policy of the points.
|
||||
RetentionPolicy string |
||||
|
||||
// Write consistency is the number of servers required to confirm write.
|
||||
WriteConsistency string |
||||
} |
||||
|
||||
// Client is a client interface for writing & querying the database.
|
||||
type Client interface { |
||||
// Ping checks that status of cluster, and will always return 0 time and no
|
||||
// error for UDP clients.
|
||||
Ping(timeout time.Duration) (time.Duration, string, error) |
||||
|
||||
// Write takes a BatchPoints object and writes all Points to InfluxDB.
|
||||
Write(bp BatchPoints) error |
||||
|
||||
// Query makes an InfluxDB Query on the database. This will fail if using
|
||||
// the UDP client.
|
||||
Query(q Query) (*Response, error) |
||||
|
||||
// Close releases any resources a Client may be using.
|
||||
Close() error |
||||
} |
||||
|
||||
// NewHTTPClient returns a new Client from the provided config.
|
||||
// Client is safe for concurrent use by multiple goroutines.
|
||||
func NewHTTPClient(conf HTTPConfig) (Client, error) { |
||||
if conf.UserAgent == "" { |
||||
conf.UserAgent = "InfluxDBClient" |
||||
} |
||||
|
||||
u, err := url.Parse(conf.Addr) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if u.Scheme != "http" && u.Scheme != "https" { |
||||
m := fmt.Sprintf("Unsupported protocol scheme: %s, your address"+ |
||||
" must start with http:// or https://", u.Scheme) |
||||
return nil, errors.New(m) |
||||
} |
||||
|
||||
tr := &http.Transport{ |
||||
TLSClientConfig: &tls.Config{ |
||||
InsecureSkipVerify: conf.InsecureSkipVerify, |
||||
}, |
||||
} |
||||
if conf.TLSConfig != nil { |
||||
tr.TLSClientConfig = conf.TLSConfig |
||||
} |
||||
return &client{ |
||||
url: *u, |
||||
username: conf.Username, |
||||
password: conf.Password, |
||||
useragent: conf.UserAgent, |
||||
httpClient: &http.Client{ |
||||
Timeout: conf.Timeout, |
||||
Transport: tr, |
||||
}, |
||||
transport: tr, |
||||
}, nil |
||||
} |
||||
|
||||
// Ping will check to see if the server is up with an optional timeout on waiting for leader.
|
||||
// Ping returns how long the request took, the version of the server it connected to, and an error if one occurred.
|
||||
func (c *client) Ping(timeout time.Duration) (time.Duration, string, error) { |
||||
now := time.Now() |
||||
u := c.url |
||||
u.Path = "ping" |
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil) |
||||
if err != nil { |
||||
return 0, "", err |
||||
} |
||||
|
||||
req.Header.Set("User-Agent", c.useragent) |
||||
|
||||
if c.username != "" { |
||||
req.SetBasicAuth(c.username, c.password) |
||||
} |
||||
|
||||
if timeout > 0 { |
||||
params := req.URL.Query() |
||||
params.Set("wait_for_leader", fmt.Sprintf("%.0fs", timeout.Seconds())) |
||||
req.URL.RawQuery = params.Encode() |
||||
} |
||||
|
||||
resp, err := c.httpClient.Do(req) |
||||
if err != nil { |
||||
return 0, "", err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return 0, "", err |
||||
} |
||||
|
||||
if resp.StatusCode != http.StatusNoContent { |
||||
var err = fmt.Errorf(string(body)) |
||||
return 0, "", err |
||||
} |
||||
|
||||
version := resp.Header.Get("X-Influxdb-Version") |
||||
return time.Since(now), version, nil |
||||
} |
||||
|
||||
// Close releases the client's resources.
|
||||
func (c *client) Close() error { |
||||
c.transport.CloseIdleConnections() |
||||
return nil |
||||
} |
||||
|
||||
// client is safe for concurrent use as the fields are all read-only
|
||||
// once the client is instantiated.
|
||||
type client struct { |
||||
// N.B - if url.UserInfo is accessed in future modifications to the
|
||||
// methods on client, you will need to syncronise access to url.
|
||||
url url.URL |
||||
username string |
||||
password string |
||||
useragent string |
||||
httpClient *http.Client |
||||
transport *http.Transport |
||||
} |
||||
|
||||
// BatchPoints is an interface into a batched grouping of points to write into
|
||||
// InfluxDB together. BatchPoints is NOT thread-safe, you must create a separate
|
||||
// batch for each goroutine.
|
||||
type BatchPoints interface { |
||||
// AddPoint adds the given point to the Batch of points.
|
||||
AddPoint(p *Point) |
||||
// AddPoints adds the given points to the Batch of points.
|
||||
AddPoints(ps []*Point) |
||||
// Points lists the points in the Batch.
|
||||
Points() []*Point |
||||
|
||||
// Precision returns the currently set precision of this Batch.
|
||||
Precision() string |
||||
// SetPrecision sets the precision of this batch.
|
||||
SetPrecision(s string) error |
||||
|
||||
// Database returns the currently set database of this Batch.
|
||||
Database() string |
||||
// SetDatabase sets the database of this Batch.
|
||||
SetDatabase(s string) |
||||
|
||||
// WriteConsistency returns the currently set write consistency of this Batch.
|
||||
WriteConsistency() string |
||||
// SetWriteConsistency sets the write consistency of this Batch.
|
||||
SetWriteConsistency(s string) |
||||
|
||||
// RetentionPolicy returns the currently set retention policy of this Batch.
|
||||
RetentionPolicy() string |
||||
// SetRetentionPolicy sets the retention policy of this Batch.
|
||||
SetRetentionPolicy(s string) |
||||
} |
||||
|
||||
// NewBatchPoints returns a BatchPoints interface based on the given config.
|
||||
func NewBatchPoints(conf BatchPointsConfig) (BatchPoints, error) { |
||||
if conf.Precision == "" { |
||||
conf.Precision = "ns" |
||||
} |
||||
if _, err := time.ParseDuration("1" + conf.Precision); err != nil { |
||||
return nil, err |
||||
} |
||||
bp := &batchpoints{ |
||||
database: conf.Database, |
||||
precision: conf.Precision, |
||||
retentionPolicy: conf.RetentionPolicy, |
||||
writeConsistency: conf.WriteConsistency, |
||||
} |
||||
return bp, nil |
||||
} |
||||
|
||||
type batchpoints struct { |
||||
points []*Point |
||||
database string |
||||
precision string |
||||
retentionPolicy string |
||||
writeConsistency string |
||||
} |
||||
|
||||
func (bp *batchpoints) AddPoint(p *Point) { |
||||
bp.points = append(bp.points, p) |
||||
} |
||||
|
||||
func (bp *batchpoints) AddPoints(ps []*Point) { |
||||
bp.points = append(bp.points, ps...) |
||||
} |
||||
|
||||
func (bp *batchpoints) Points() []*Point { |
||||
return bp.points |
||||
} |
||||
|
||||
func (bp *batchpoints) Precision() string { |
||||
return bp.precision |
||||
} |
||||
|
||||
func (bp *batchpoints) Database() string { |
||||
return bp.database |
||||
} |
||||
|
||||
func (bp *batchpoints) WriteConsistency() string { |
||||
return bp.writeConsistency |
||||
} |
||||
|
||||
func (bp *batchpoints) RetentionPolicy() string { |
||||
return bp.retentionPolicy |
||||
} |
||||
|
||||
func (bp *batchpoints) SetPrecision(p string) error { |
||||
if _, err := time.ParseDuration("1" + p); err != nil { |
||||
return err |
||||
} |
||||
bp.precision = p |
||||
return nil |
||||
} |
||||
|
||||
func (bp *batchpoints) SetDatabase(db string) { |
||||
bp.database = db |
||||
} |
||||
|
||||
func (bp *batchpoints) SetWriteConsistency(wc string) { |
||||
bp.writeConsistency = wc |
||||
} |
||||
|
||||
func (bp *batchpoints) SetRetentionPolicy(rp string) { |
||||
bp.retentionPolicy = rp |
||||
} |
||||
|
||||
// Point represents a single data point.
|
||||
type Point struct { |
||||
pt models.Point |
||||
} |
||||
|
||||
// NewPoint returns a point with the given timestamp. If a timestamp is not
|
||||
// given, then data is sent to the database without a timestamp, in which case
|
||||
// the server will assign local time upon reception. NOTE: it is recommended to
|
||||
// send data with a timestamp.
|
||||
func NewPoint( |
||||
name string, |
||||
tags map[string]string, |
||||
fields map[string]interface{}, |
||||
t ...time.Time, |
||||
) (*Point, error) { |
||||
var T time.Time |
||||
if len(t) > 0 { |
||||
T = t[0] |
||||
} |
||||
|
||||
pt, err := models.NewPoint(name, models.NewTags(tags), fields, T) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &Point{ |
||||
pt: pt, |
||||
}, nil |
||||
} |
||||
|
||||
// String returns a line-protocol string of the Point.
|
||||
func (p *Point) String() string { |
||||
return p.pt.String() |
||||
} |
||||
|
||||
// PrecisionString returns a line-protocol string of the Point,
|
||||
// with the timestamp formatted for the given precision.
|
||||
func (p *Point) PrecisionString(precison string) string { |
||||
return p.pt.PrecisionString(precison) |
||||
} |
||||
|
||||
// Name returns the measurement name of the point.
|
||||
func (p *Point) Name() string { |
||||
return string(p.pt.Name()) |
||||
} |
||||
|
||||
// Tags returns the tags associated with the point.
|
||||
func (p *Point) Tags() map[string]string { |
||||
return p.pt.Tags().Map() |
||||
} |
||||
|
||||
// Time return the timestamp for the point.
|
||||
func (p *Point) Time() time.Time { |
||||
return p.pt.Time() |
||||
} |
||||
|
||||
// UnixNano returns timestamp of the point in nanoseconds since Unix epoch.
|
||||
func (p *Point) UnixNano() int64 { |
||||
return p.pt.UnixNano() |
||||
} |
||||
|
||||
// Fields returns the fields for the point.
|
||||
func (p *Point) Fields() (map[string]interface{}, error) { |
||||
return p.pt.Fields() |
||||
} |
||||
|
||||
// NewPointFrom returns a point from the provided models.Point.
|
||||
func NewPointFrom(pt models.Point) *Point { |
||||
return &Point{pt: pt} |
||||
} |
||||
|
||||
func (c *client) Write(bp BatchPoints) error { |
||||
var b bytes.Buffer |
||||
|
||||
for _, p := range bp.Points() { |
||||
if _, err := b.WriteString(p.pt.PrecisionString(bp.Precision())); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := b.WriteByte('\n'); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
u := c.url |
||||
u.Path = "write" |
||||
req, err := http.NewRequest("POST", u.String(), &b) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
req.Header.Set("Content-Type", "") |
||||
req.Header.Set("User-Agent", c.useragent) |
||||
if c.username != "" { |
||||
req.SetBasicAuth(c.username, c.password) |
||||
} |
||||
|
||||
params := req.URL.Query() |
||||
params.Set("db", bp.Database()) |
||||
params.Set("rp", bp.RetentionPolicy()) |
||||
params.Set("precision", bp.Precision()) |
||||
params.Set("consistency", bp.WriteConsistency()) |
||||
req.URL.RawQuery = params.Encode() |
||||
|
||||
resp, err := c.httpClient.Do(req) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK { |
||||
var err = fmt.Errorf(string(body)) |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Query defines a query to send to the server.
|
||||
type Query struct { |
||||
Command string |
||||
Database string |
||||
Precision string |
||||
Chunked bool |
||||
ChunkSize int |
||||
Parameters map[string]interface{} |
||||
} |
||||
|
||||
// NewQuery returns a query object.
|
||||
// The database and precision arguments can be empty strings if they are not needed for the query.
|
||||
func NewQuery(command, database, precision string) Query { |
||||
return Query{ |
||||
Command: command, |
||||
Database: database, |
||||
Precision: precision, |
||||
Parameters: make(map[string]interface{}), |
||||
} |
||||
} |
||||
|
||||
// NewQueryWithParameters returns a query object.
|
||||
// The database and precision arguments can be empty strings if they are not needed for the query.
|
||||
// parameters is a map of the parameter names used in the command to their values.
|
||||
func NewQueryWithParameters(command, database, precision string, parameters map[string]interface{}) Query { |
||||
return Query{ |
||||
Command: command, |
||||
Database: database, |
||||
Precision: precision, |
||||
Parameters: parameters, |
||||
} |
||||
} |
||||
|
||||
// Response represents a list of statement results.
|
||||
type Response struct { |
||||
Results []Result |
||||
Err string `json:"error,omitempty"` |
||||
} |
||||
|
||||
// Error returns the first error from any statement.
|
||||
// It returns nil if no errors occurred on any statements.
|
||||
func (r *Response) Error() error { |
||||
if r.Err != "" { |
||||
return fmt.Errorf(r.Err) |
||||
} |
||||
for _, result := range r.Results { |
||||
if result.Err != "" { |
||||
return fmt.Errorf(result.Err) |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Message represents a user message.
|
||||
type Message struct { |
||||
Level string |
||||
Text string |
||||
} |
||||
|
||||
// Result represents a resultset returned from a single statement.
|
||||
type Result struct { |
||||
Series []models.Row |
||||
Messages []*Message |
||||
Err string `json:"error,omitempty"` |
||||
} |
||||
|
||||
// Query sends a command to the server and returns the Response.
|
||||
func (c *client) Query(q Query) (*Response, error) { |
||||
u := c.url |
||||
u.Path = "query" |
||||
|
||||
jsonParameters, err := json.Marshal(q.Parameters) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
req, err := http.NewRequest("POST", u.String(), nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
req.Header.Set("Content-Type", "") |
||||
req.Header.Set("User-Agent", c.useragent) |
||||
|
||||
if c.username != "" { |
||||
req.SetBasicAuth(c.username, c.password) |
||||
} |
||||
|
||||
params := req.URL.Query() |
||||
params.Set("q", q.Command) |
||||
params.Set("db", q.Database) |
||||
params.Set("params", string(jsonParameters)) |
||||
if q.Chunked { |
||||
params.Set("chunked", "true") |
||||
if q.ChunkSize > 0 { |
||||
params.Set("chunk_size", strconv.Itoa(q.ChunkSize)) |
||||
} |
||||
} |
||||
|
||||
if q.Precision != "" { |
||||
params.Set("epoch", q.Precision) |
||||
} |
||||
req.URL.RawQuery = params.Encode() |
||||
|
||||
resp, err := c.httpClient.Do(req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
// If we lack a X-Influxdb-Version header, then we didn't get a response from influxdb
|
||||
// but instead some other service. If the error code is also a 500+ code, then some
|
||||
// downstream loadbalancer/proxy/etc had an issue and we should report that.
|
||||
if resp.Header.Get("X-Influxdb-Version") == "" && resp.StatusCode >= http.StatusInternalServerError { |
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil || len(body) == 0 { |
||||
return nil, fmt.Errorf("received status code %d from downstream server", resp.StatusCode) |
||||
} |
||||
|
||||
return nil, fmt.Errorf("received status code %d from downstream server, with response body: %q", resp.StatusCode, body) |
||||
} |
||||
|
||||
// If we get an unexpected content type, then it is also not from influx direct and therefore
|
||||
// we want to know what we received and what status code was returned for debugging purposes.
|
||||
if cType, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type")); cType != "application/json" { |
||||
// Read up to 1kb of the body to help identify downstream errors and limit the impact of things
|
||||
// like downstream serving a large file
|
||||
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024)) |
||||
if err != nil || len(body) == 0 { |
||||
return nil, fmt.Errorf("expected json response, got empty body, with status: %v", resp.StatusCode) |
||||
} |
||||
|
||||
return nil, fmt.Errorf("expected json response, got %q, with status: %v and response body: %q", cType, resp.StatusCode, body) |
||||
} |
||||
|
||||
var response Response |
||||
if q.Chunked { |
||||
cr := NewChunkedResponse(resp.Body) |
||||
for { |
||||
r, err := cr.NextResponse() |
||||
if err != nil { |
||||
// If we got an error while decoding the response, send that back.
|
||||
return nil, err |
||||
} |
||||
|
||||
if r == nil { |
||||
break |
||||
} |
||||
|
||||
response.Results = append(response.Results, r.Results...) |
||||
if r.Err != "" { |
||||
response.Err = r.Err |
||||
break |
||||
} |
||||
} |
||||
} else { |
||||
dec := json.NewDecoder(resp.Body) |
||||
dec.UseNumber() |
||||
decErr := dec.Decode(&response) |
||||
|
||||
// ignore this error if we got an invalid status code
|
||||
if decErr != nil && decErr.Error() == "EOF" && resp.StatusCode != http.StatusOK { |
||||
decErr = nil |
||||
} |
||||
// If we got a valid decode error, send that back
|
||||
if decErr != nil { |
||||
return nil, fmt.Errorf("unable to decode json: received status code %d err: %s", resp.StatusCode, decErr) |
||||
} |
||||
} |
||||
|
||||
// If we don't have an error in our json response, and didn't get statusOK
|
||||
// then send back an error
|
||||
if resp.StatusCode != http.StatusOK && response.Error() == nil { |
||||
return &response, fmt.Errorf("received status code %d from server", resp.StatusCode) |
||||
} |
||||
return &response, nil |
||||
} |
||||
|
||||
// duplexReader reads responses and writes it to another writer while
|
||||
// satisfying the reader interface.
|
||||
type duplexReader struct { |
||||
r io.Reader |
||||
w io.Writer |
||||
} |
||||
|
||||
func (r *duplexReader) Read(p []byte) (n int, err error) { |
||||
n, err = r.r.Read(p) |
||||
if err == nil { |
||||
r.w.Write(p[:n]) |
||||
} |
||||
return n, err |
||||
} |
||||
|
||||
// ChunkedResponse represents a response from the server that
|
||||
// uses chunking to stream the output.
|
||||
type ChunkedResponse struct { |
||||
dec *json.Decoder |
||||
duplex *duplexReader |
||||
buf bytes.Buffer |
||||
} |
||||
|
||||
// NewChunkedResponse reads a stream and produces responses from the stream.
|
||||
func NewChunkedResponse(r io.Reader) *ChunkedResponse { |
||||
resp := &ChunkedResponse{} |
||||
resp.duplex = &duplexReader{r: r, w: &resp.buf} |
||||
resp.dec = json.NewDecoder(resp.duplex) |
||||
resp.dec.UseNumber() |
||||
return resp |
||||
} |
||||
|
||||
// NextResponse reads the next line of the stream and returns a response.
|
||||
func (r *ChunkedResponse) NextResponse() (*Response, error) { |
||||
var response Response |
||||
|
||||
if err := r.dec.Decode(&response); err != nil { |
||||
if err == io.EOF { |
||||
return nil, nil |
||||
} |
||||
// A decoding error happened. This probably means the server crashed
|
||||
// and sent a last-ditch error message to us. Ensure we have read the
|
||||
// entirety of the connection to get any remaining error text.
|
||||
io.Copy(ioutil.Discard, r.duplex) |
||||
return nil, errors.New(strings.TrimSpace(r.buf.String())) |
||||
} |
||||
|
||||
r.buf.Reset() |
||||
return &response, nil |
||||
} |
@ -0,0 +1,112 @@ |
||||
package client |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"net" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
// UDPPayloadSize is a reasonable default payload size for UDP packets that
|
||||
// could be travelling over the internet.
|
||||
UDPPayloadSize = 512 |
||||
) |
||||
|
||||
// UDPConfig is the config data needed to create a UDP Client.
|
||||
type UDPConfig struct { |
||||
// Addr should be of the form "host:port"
|
||||
// or "[ipv6-host%zone]:port".
|
||||
Addr string |
||||
|
||||
// PayloadSize is the maximum size of a UDP client message, optional
|
||||
// Tune this based on your network. Defaults to UDPPayloadSize.
|
||||
PayloadSize int |
||||
} |
||||
|
||||
// NewUDPClient returns a client interface for writing to an InfluxDB UDP
|
||||
// service from the given config.
|
||||
func NewUDPClient(conf UDPConfig) (Client, error) { |
||||
var udpAddr *net.UDPAddr |
||||
udpAddr, err := net.ResolveUDPAddr("udp", conf.Addr) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
conn, err := net.DialUDP("udp", nil, udpAddr) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
payloadSize := conf.PayloadSize |
||||
if payloadSize == 0 { |
||||
payloadSize = UDPPayloadSize |
||||
} |
||||
|
||||
return &udpclient{ |
||||
conn: conn, |
||||
payloadSize: payloadSize, |
||||
}, nil |
||||
} |
||||
|
||||
// Close releases the udpclient's resources.
|
||||
func (uc *udpclient) Close() error { |
||||
return uc.conn.Close() |
||||
} |
||||
|
||||
type udpclient struct { |
||||
conn io.WriteCloser |
||||
payloadSize int |
||||
} |
||||
|
||||
func (uc *udpclient) Write(bp BatchPoints) error { |
||||
var b = make([]byte, 0, uc.payloadSize) // initial buffer size, it will grow as needed
|
||||
var d, _ = time.ParseDuration("1" + bp.Precision()) |
||||
|
||||
var delayedError error |
||||
|
||||
var checkBuffer = func(n int) { |
||||
if len(b) > 0 && len(b)+n > uc.payloadSize { |
||||
if _, err := uc.conn.Write(b); err != nil { |
||||
delayedError = err |
||||
} |
||||
b = b[:0] |
||||
} |
||||
} |
||||
|
||||
for _, p := range bp.Points() { |
||||
p.pt.Round(d) |
||||
pointSize := p.pt.StringSize() + 1 // include newline in size
|
||||
//point := p.pt.RoundedString(d) + "\n"
|
||||
|
||||
checkBuffer(pointSize) |
||||
|
||||
if p.Time().IsZero() || pointSize <= uc.payloadSize { |
||||
b = p.pt.AppendString(b) |
||||
b = append(b, '\n') |
||||
continue |
||||
} |
||||
|
||||
points := p.pt.Split(uc.payloadSize - 1) // account for newline character
|
||||
for _, sp := range points { |
||||
checkBuffer(sp.StringSize() + 1) |
||||
b = sp.AppendString(b) |
||||
b = append(b, '\n') |
||||
} |
||||
} |
||||
|
||||
if len(b) > 0 { |
||||
if _, err := uc.conn.Write(b); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return delayedError |
||||
} |
||||
|
||||
func (uc *udpclient) Query(q Query) (*Response, error) { |
||||
return nil, fmt.Errorf("Querying via UDP is not supported") |
||||
} |
||||
|
||||
func (uc *udpclient) Ping(timeout time.Duration) (time.Duration, string, error) { |
||||
return 0, "", nil |
||||
} |
@ -0,0 +1,48 @@ |
||||
package models |
||||
|
||||
import ( |
||||
"errors" |
||||
"strings" |
||||
) |
||||
|
||||
// ConsistencyLevel represent a required replication criteria before a write can
|
||||
// be returned as successful.
|
||||
//
|
||||
// The consistency level is handled in open-source InfluxDB but only applicable to clusters.
|
||||
type ConsistencyLevel int |
||||
|
||||
const ( |
||||
// ConsistencyLevelAny allows for hinted handoff, potentially no write happened yet.
|
||||
ConsistencyLevelAny ConsistencyLevel = iota |
||||
|
||||
// ConsistencyLevelOne requires at least one data node acknowledged a write.
|
||||
ConsistencyLevelOne |
||||
|
||||
// ConsistencyLevelQuorum requires a quorum of data nodes to acknowledge a write.
|
||||
ConsistencyLevelQuorum |
||||
|
||||
// ConsistencyLevelAll requires all data nodes to acknowledge a write.
|
||||
ConsistencyLevelAll |
||||
) |
||||
|
||||
var ( |
||||
// ErrInvalidConsistencyLevel is returned when parsing the string version
|
||||
// of a consistency level.
|
||||
ErrInvalidConsistencyLevel = errors.New("invalid consistency level") |
||||
) |
||||
|
||||
// ParseConsistencyLevel converts a consistency level string to the corresponding ConsistencyLevel const.
|
||||
func ParseConsistencyLevel(level string) (ConsistencyLevel, error) { |
||||
switch strings.ToLower(level) { |
||||
case "any": |
||||
return ConsistencyLevelAny, nil |
||||
case "one": |
||||
return ConsistencyLevelOne, nil |
||||
case "quorum": |
||||
return ConsistencyLevelQuorum, nil |
||||
case "all": |
||||
return ConsistencyLevelAll, nil |
||||
default: |
||||
return 0, ErrInvalidConsistencyLevel |
||||
} |
||||
} |
@ -0,0 +1,32 @@ |
||||
package models // import "github.com/influxdata/influxdb/models"
|
||||
|
||||
// from stdlib hash/fnv/fnv.go
|
||||
const ( |
||||
prime64 = 1099511628211 |
||||
offset64 = 14695981039346656037 |
||||
) |
||||
|
||||
// InlineFNV64a is an alloc-free port of the standard library's fnv64a.
|
||||
// See https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function.
|
||||
type InlineFNV64a uint64 |
||||
|
||||
// NewInlineFNV64a returns a new instance of InlineFNV64a.
|
||||
func NewInlineFNV64a() InlineFNV64a { |
||||
return offset64 |
||||
} |
||||
|
||||
// Write adds data to the running hash.
|
||||
func (s *InlineFNV64a) Write(data []byte) (int, error) { |
||||
hash := uint64(*s) |
||||
for _, c := range data { |
||||
hash ^= uint64(c) |
||||
hash *= prime64 |
||||
} |
||||
*s = InlineFNV64a(hash) |
||||
return len(data), nil |
||||
} |
||||
|
||||
// Sum64 returns the uint64 of the current resulting hash.
|
||||
func (s *InlineFNV64a) Sum64() uint64 { |
||||
return uint64(*s) |
||||
} |
@ -0,0 +1,44 @@ |
||||
package models // import "github.com/influxdata/influxdb/models"
|
||||
|
||||
import ( |
||||
"reflect" |
||||
"strconv" |
||||
"unsafe" |
||||
) |
||||
|
||||
// parseIntBytes is a zero-alloc wrapper around strconv.ParseInt.
|
||||
func parseIntBytes(b []byte, base int, bitSize int) (i int64, err error) { |
||||
s := unsafeBytesToString(b) |
||||
return strconv.ParseInt(s, base, bitSize) |
||||
} |
||||
|
||||
// parseUintBytes is a zero-alloc wrapper around strconv.ParseUint.
|
||||
func parseUintBytes(b []byte, base int, bitSize int) (i uint64, err error) { |
||||
s := unsafeBytesToString(b) |
||||
return strconv.ParseUint(s, base, bitSize) |
||||
} |
||||
|
||||
// parseFloatBytes is a zero-alloc wrapper around strconv.ParseFloat.
|
||||
func parseFloatBytes(b []byte, bitSize int) (float64, error) { |
||||
s := unsafeBytesToString(b) |
||||
return strconv.ParseFloat(s, bitSize) |
||||
} |
||||
|
||||
// parseBoolBytes is a zero-alloc wrapper around strconv.ParseBool.
|
||||
func parseBoolBytes(b []byte) (bool, error) { |
||||
return strconv.ParseBool(unsafeBytesToString(b)) |
||||
} |
||||
|
||||
// unsafeBytesToString converts a []byte to a string without a heap allocation.
|
||||
//
|
||||
// It is unsafe, and is intended to prepare input to short-lived functions
|
||||
// that require strings.
|
||||
func unsafeBytesToString(in []byte) string { |
||||
src := *(*reflect.SliceHeader)(unsafe.Pointer(&in)) |
||||
dst := reflect.StringHeader{ |
||||
Data: src.Data, |
||||
Len: src.Len, |
||||
} |
||||
s := *(*string)(unsafe.Pointer(&dst)) |
||||
return s |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,62 @@ |
||||
package models |
||||
|
||||
import ( |
||||
"sort" |
||||
) |
||||
|
||||
// Row represents a single row returned from the execution of a statement.
|
||||
type Row struct { |
||||
Name string `json:"name,omitempty"` |
||||
Tags map[string]string `json:"tags,omitempty"` |
||||
Columns []string `json:"columns,omitempty"` |
||||
Values [][]interface{} `json:"values,omitempty"` |
||||
Partial bool `json:"partial,omitempty"` |
||||
} |
||||
|
||||
// SameSeries returns true if r contains values for the same series as o.
|
||||
func (r *Row) SameSeries(o *Row) bool { |
||||
return r.tagsHash() == o.tagsHash() && r.Name == o.Name |
||||
} |
||||
|
||||
// tagsHash returns a hash of tag key/value pairs.
|
||||
func (r *Row) tagsHash() uint64 { |
||||
h := NewInlineFNV64a() |
||||
keys := r.tagsKeys() |
||||
for _, k := range keys { |
||||
h.Write([]byte(k)) |
||||
h.Write([]byte(r.Tags[k])) |
||||
} |
||||
return h.Sum64() |
||||
} |
||||
|
||||
// tagKeys returns a sorted list of tag keys.
|
||||
func (r *Row) tagsKeys() []string { |
||||
a := make([]string, 0, len(r.Tags)) |
||||
for k := range r.Tags { |
||||
a = append(a, k) |
||||
} |
||||
sort.Strings(a) |
||||
return a |
||||
} |
||||
|
||||
// Rows represents a collection of rows. Rows implements sort.Interface.
|
||||
type Rows []*Row |
||||
|
||||
// Len implements sort.Interface.
|
||||
func (p Rows) Len() int { return len(p) } |
||||
|
||||
// Less implements sort.Interface.
|
||||
func (p Rows) Less(i, j int) bool { |
||||
// Sort by name first.
|
||||
if p[i].Name != p[j].Name { |
||||
return p[i].Name < p[j].Name |
||||
} |
||||
|
||||
// Sort by tag set hash. Tags don't have a meaningful sort order so we
|
||||
// just compute a hash and sort by that instead. This allows the tests
|
||||
// to receive rows in a predictable order every time.
|
||||
return p[i].tagsHash() < p[j].tagsHash() |
||||
} |
||||
|
||||
// Swap implements sort.Interface.
|
||||
func (p Rows) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |
@ -0,0 +1,42 @@ |
||||
package models |
||||
|
||||
// Statistic is the representation of a statistic used by the monitoring service.
|
||||
type Statistic struct { |
||||
Name string `json:"name"` |
||||
Tags map[string]string `json:"tags"` |
||||
Values map[string]interface{} `json:"values"` |
||||
} |
||||
|
||||
// NewStatistic returns an initialized Statistic.
|
||||
func NewStatistic(name string) Statistic { |
||||
return Statistic{ |
||||
Name: name, |
||||
Tags: make(map[string]string), |
||||
Values: make(map[string]interface{}), |
||||
} |
||||
} |
||||
|
||||
// StatisticTags is a map that can be merged with others without causing
|
||||
// mutations to either map.
|
||||
type StatisticTags map[string]string |
||||
|
||||
// Merge creates a new map containing the merged contents of tags and t.
|
||||
// If both tags and the receiver map contain the same key, the value in tags
|
||||
// is used in the resulting map.
|
||||
//
|
||||
// Merge always returns a usable map.
|
||||
func (t StatisticTags) Merge(tags map[string]string) map[string]string { |
||||
// Add everything in tags to the result.
|
||||
out := make(map[string]string, len(tags)) |
||||
for k, v := range tags { |
||||
out[k] = v |
||||
} |
||||
|
||||
// Only add values from t that don't appear in tags.
|
||||
for k, v := range t { |
||||
if _, ok := tags[k]; !ok { |
||||
out[k] = v |
||||
} |
||||
} |
||||
return out |
||||
} |
@ -0,0 +1,74 @@ |
||||
package models |
||||
|
||||
// Helper time methods since parsing time can easily overflow and we only support a
|
||||
// specific time range.
|
||||
|
||||
import ( |
||||
"fmt" |
||||
"math" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
// MinNanoTime is the minumum time that can be represented.
|
||||
//
|
||||
// 1677-09-21 00:12:43.145224194 +0000 UTC
|
||||
//
|
||||
// The two lowest minimum integers are used as sentinel values. The
|
||||
// minimum value needs to be used as a value lower than any other value for
|
||||
// comparisons and another separate value is needed to act as a sentinel
|
||||
// default value that is unusable by the user, but usable internally.
|
||||
// Because these two values need to be used for a special purpose, we do
|
||||
// not allow users to write points at these two times.
|
||||
MinNanoTime = int64(math.MinInt64) + 2 |
||||
|
||||
// MaxNanoTime is the maximum time that can be represented.
|
||||
//
|
||||
// 2262-04-11 23:47:16.854775806 +0000 UTC
|
||||
//
|
||||
// The highest time represented by a nanosecond needs to be used for an
|
||||
// exclusive range in the shard group, so the maximum time needs to be one
|
||||
// less than the possible maximum number of nanoseconds representable by an
|
||||
// int64 so that we don't lose a point at that one time.
|
||||
MaxNanoTime = int64(math.MaxInt64) - 1 |
||||
) |
||||
|
||||
var ( |
||||
minNanoTime = time.Unix(0, MinNanoTime).UTC() |
||||
maxNanoTime = time.Unix(0, MaxNanoTime).UTC() |
||||
|
||||
// ErrTimeOutOfRange gets returned when time is out of the representable range using int64 nanoseconds since the epoch.
|
||||
ErrTimeOutOfRange = fmt.Errorf("time outside range %d - %d", MinNanoTime, MaxNanoTime) |
||||
) |
||||
|
||||
// SafeCalcTime safely calculates the time given. Will return error if the time is outside the
|
||||
// supported range.
|
||||
func SafeCalcTime(timestamp int64, precision string) (time.Time, error) { |
||||
mult := GetPrecisionMultiplier(precision) |
||||
if t, ok := safeSignedMult(timestamp, mult); ok { |
||||
tme := time.Unix(0, t).UTC() |
||||
return tme, CheckTime(tme) |
||||
} |
||||
|
||||
return time.Time{}, ErrTimeOutOfRange |
||||
} |
||||
|
||||
// CheckTime checks that a time is within the safe range.
|
||||
func CheckTime(t time.Time) error { |
||||
if t.Before(minNanoTime) || t.After(maxNanoTime) { |
||||
return ErrTimeOutOfRange |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Perform the multiplication and check to make sure it didn't overflow.
|
||||
func safeSignedMult(a, b int64) (int64, bool) { |
||||
if a == 0 || b == 0 || a == 1 || b == 1 { |
||||
return a * b, true |
||||
} |
||||
if a == MinNanoTime || b == MaxNanoTime { |
||||
return 0, false |
||||
} |
||||
c := a * b |
||||
return c, c/b == a |
||||
} |
@ -0,0 +1,7 @@ |
||||
// +build uint uint64
|
||||
|
||||
package models |
||||
|
||||
func init() { |
||||
EnableUintSupport() |
||||
} |
@ -0,0 +1,115 @@ |
||||
// Package escape contains utilities for escaping parts of InfluxQL
|
||||
// and InfluxDB line protocol.
|
||||
package escape // import "github.com/influxdata/influxdb/pkg/escape"
|
||||
|
||||
import ( |
||||
"bytes" |
||||
"strings" |
||||
) |
||||
|
||||
// Codes is a map of bytes to be escaped.
|
||||
var Codes = map[byte][]byte{ |
||||
',': []byte(`\,`), |
||||
'"': []byte(`\"`), |
||||
' ': []byte(`\ `), |
||||
'=': []byte(`\=`), |
||||
} |
||||
|
||||
// Bytes escapes characters on the input slice, as defined by Codes.
|
||||
func Bytes(in []byte) []byte { |
||||
for b, esc := range Codes { |
||||
in = bytes.Replace(in, []byte{b}, esc, -1) |
||||
} |
||||
return in |
||||
} |
||||
|
||||
const escapeChars = `," =` |
||||
|
||||
// IsEscaped returns whether b has any escaped characters,
|
||||
// i.e. whether b seems to have been processed by Bytes.
|
||||
func IsEscaped(b []byte) bool { |
||||
for len(b) > 0 { |
||||
i := bytes.IndexByte(b, '\\') |
||||
if i < 0 { |
||||
return false |
||||
} |
||||
|
||||
if i+1 < len(b) && strings.IndexByte(escapeChars, b[i+1]) >= 0 { |
||||
return true |
||||
} |
||||
b = b[i+1:] |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// AppendUnescaped appends the unescaped version of src to dst
|
||||
// and returns the resulting slice.
|
||||
func AppendUnescaped(dst, src []byte) []byte { |
||||
var pos int |
||||
for len(src) > 0 { |
||||
next := bytes.IndexByte(src[pos:], '\\') |
||||
if next < 0 || pos+next+1 >= len(src) { |
||||
return append(dst, src...) |
||||
} |
||||
|
||||
if pos+next+1 < len(src) && strings.IndexByte(escapeChars, src[pos+next+1]) >= 0 { |
||||
if pos+next > 0 { |
||||
dst = append(dst, src[:pos+next]...) |
||||
} |
||||
src = src[pos+next+1:] |
||||
pos = 0 |
||||
} else { |
||||
pos += next + 1 |
||||
} |
||||
} |
||||
|
||||
return dst |
||||
} |
||||
|
||||
// Unescape returns a new slice containing the unescaped version of in.
|
||||
func Unescape(in []byte) []byte { |
||||
if len(in) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
if bytes.IndexByte(in, '\\') == -1 { |
||||
return in |
||||
} |
||||
|
||||
i := 0 |
||||
inLen := len(in) |
||||
|
||||
// The output size will be no more than inLen. Preallocating the
|
||||
// capacity of the output is faster and uses less memory than
|
||||
// letting append() do its own (over)allocation.
|
||||
out := make([]byte, 0, inLen) |
||||
|
||||
for { |
||||
if i >= inLen { |
||||
break |
||||
} |
||||
if in[i] == '\\' && i+1 < inLen { |
||||
switch in[i+1] { |
||||
case ',': |
||||
out = append(out, ',') |
||||
i += 2 |
||||
continue |
||||
case '"': |
||||
out = append(out, '"') |
||||
i += 2 |
||||
continue |
||||
case ' ': |
||||
out = append(out, ' ') |
||||
i += 2 |
||||
continue |
||||
case '=': |
||||
out = append(out, '=') |
||||
i += 2 |
||||
continue |
||||
} |
||||
} |
||||
out = append(out, in[i]) |
||||
i += 1 |
||||
} |
||||
return out |
||||
} |
@ -0,0 +1,21 @@ |
||||
package escape |
||||
|
||||
import "strings" |
||||
|
||||
var ( |
||||
escaper = strings.NewReplacer(`,`, `\,`, `"`, `\"`, ` `, `\ `, `=`, `\=`) |
||||
unescaper = strings.NewReplacer(`\,`, `,`, `\"`, `"`, `\ `, ` `, `\=`, `=`) |
||||
) |
||||
|
||||
// UnescapeString returns unescaped version of in.
|
||||
func UnescapeString(in string) string { |
||||
if strings.IndexByte(in, '\\') == -1 { |
||||
return in |
||||
} |
||||
return unescaper.Replace(in) |
||||
} |
||||
|
||||
// String returns the escaped version of in.
|
||||
func String(in string) string { |
||||
return escaper.Replace(in) |
||||
} |
@ -1,87 +0,0 @@ |
||||
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) |
||||
} |
||||
|
||||
func (p *PrefixedRegistry) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(p.underlying) |
||||
} |
@ -1,13 +0,0 @@ |
||||
// 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 |
Loading…
Reference in new issue