diff --git a/etcdserver/etcdhttp/client.go b/etcdserver/etcdhttp/client.go index b26b1325c..6e44e3396 100644 --- a/etcdserver/etcdhttp/client.go +++ b/etcdserver/etcdhttp/client.go @@ -37,6 +37,7 @@ import ( "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes" "github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/etcdserver/stats" + "github.com/coreos/etcd/pkg/metrics" "github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/raft" "github.com/coreos/etcd/store" @@ -292,7 +293,7 @@ func serveStats(w http.ResponseWriter, r *http.Request) { // TODO: getting one key or a prefix of keys based on path fmt.Fprintf(w, "{\n") first := true - expvar.Do(func(kv expvar.KeyValue) { + metrics.Do(func(kv expvar.KeyValue) { if !first { fmt.Fprintf(w, ",\n") } diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 009935e9f..10c8ec168 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -28,15 +28,13 @@ import ( "bytes" "expvar" "fmt" + "sort" + "sync" ) // Counter is a number that increases over time monotonically. type Counter struct{ i *expvar.Int } -func NewCounter(name string) *Counter { - return &Counter{i: expvar.NewInt(name)} -} - func (c *Counter) Add() { c.i.Add(1) } func (c *Counter) AddBy(delta int64) { c.i.Add(delta) } @@ -46,10 +44,6 @@ func (c *Counter) String() string { return c.i.String() } // Gauge returns instantaneous value that is expected to fluctuate over time. type Gauge struct{ i *expvar.Int } -func NewGauge(name string) *Gauge { - return &Gauge{i: expvar.NewInt(name)} -} - func (g *Gauge) Set(value int64) { g.i.Set(value) } func (g *Gauge) String() string { return g.i.String() } @@ -61,19 +55,6 @@ func (v *nilVar) String() string { return "nil" } // Map aggregates Counters and Gauges. type Map struct{ *expvar.Map } -func NewMap(name string) *Map { - return &Map{Map: expvar.NewMap(name)} -} - -// GetMap returns the map if it exists, or inits the given name map if it does -// not exist. -func GetMap(name string) *Map { - if m, ok := expvar.Get(name).(*expvar.Map); ok { - return &Map{Map: m} - } - return NewMap(name) -} - func (m *Map) NewCounter(key string) *Counter { c := &Counter{i: new(expvar.Int)} m.Set(key, c) @@ -109,3 +90,76 @@ func (m *Map) String() string { fmt.Fprintf(&b, "}") return b.String() } + +// All published variables. +var ( + mutex sync.RWMutex + vars = make(map[string]expvar.Var) + varKeys []string // sorted +) + +// Publish declares a named exported variable. +// If the name is already registered then this will overwrite the old one. +func Publish(name string, v expvar.Var) { + mutex.Lock() + defer mutex.Unlock() + if _, existing := vars[name]; !existing { + varKeys = append(varKeys, name) + } + sort.Strings(varKeys) + vars[name] = v + return +} + +// Get retrieves a named exported variable. +func Get(name string) expvar.Var { + mutex.RLock() + defer mutex.RUnlock() + return vars[name] +} + +// Convenience functions for creating new exported variables. +func NewCounter(name string) *Counter { + c := &Counter{i: new(expvar.Int)} + Publish(name, c) + return c +} + +func NewGauge(name string) *Gauge { + g := &Gauge{i: new(expvar.Int)} + Publish(name, g) + return g +} + +func NewMap(name string) *Map { + m := &Map{Map: new(expvar.Map).Init()} + Publish(name, m) + return m +} + +// GetMap returns the map if it exists, or inits the given name map if it does +// not exist. +func GetMap(name string) *Map { + v := Get(name) + if v == nil { + return NewMap(name) + } + return v.(*Map) +} + +// Do calls f for each exported variable. +// The global variable map is locked during the iteration, +// but existing entries may be concurrently updated. +func Do(f func(expvar.KeyValue)) { + mutex.RLock() + defer mutex.RUnlock() + for _, k := range varKeys { + f(expvar.KeyValue{k, vars[k]}) + } +} + +// for test only +func reset() { + vars = make(map[string]expvar.Var) + varKeys = nil +} diff --git a/pkg/metrics/metrics_test.go b/pkg/metrics/metrics_test.go index 77191ed37..d4327fe0b 100644 --- a/pkg/metrics/metrics_test.go +++ b/pkg/metrics/metrics_test.go @@ -21,8 +21,39 @@ import ( "testing" ) -// TestMetrics tests the basic usage of metrics. -func TestMetrics(t *testing.T) { +// TestPublish tests function Publish and related creation functions. +func TestPublish(t *testing.T) { + defer reset() + Publish("string", new(expvar.String)) + NewCounter("counter") + NewGauge("gauge") + NewMap("map") + + keys := []string{"counter", "gauge", "map", "string"} + i := 0 + Do(func(kv expvar.KeyValue) { + if kv.Key != keys[i] { + t.Errorf("#%d: key = %s, want %s", i, kv.Key, keys[i]) + } + i++ + }) +} + +func TestDuplicatePublish(t *testing.T) { + defer reset() + num1 := new(expvar.Int) + num1.Set(10) + Publish("number", num1) + num2 := new(expvar.Int) + num2.Set(20) + Publish("number", num2) + if g := Get("number").String(); g != "20" { + t.Errorf("number str = %s, want %s", g, "20") + } +} + +// TestMap tests the basic usage of Map. +func TestMap(t *testing.T) { m := &Map{Map: new(expvar.Map).Init()} c := m.NewCounter("number")