Browse Source

Merge pull request #2156 from yichengq/309

pkg/metrics: self-manage global expvar map
Yicheng Qin 11 years ago
parent
commit
f0c9a54edb
3 changed files with 110 additions and 24 deletions
  1. 2 1
      etcdserver/etcdhttp/client.go
  2. 75 21
      pkg/metrics/metrics.go
  3. 33 2
      pkg/metrics/metrics_test.go

+ 2 - 1
etcdserver/etcdhttp/client.go

@@ -35,6 +35,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"
@@ -290,7 +291,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")
 		}

+ 75 - 21
pkg/metrics/metrics.go

@@ -26,15 +26,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) }
@@ -44,10 +42,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() }
@@ -59,19 +53,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)
@@ -107,3 +88,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
+}

+ 33 - 2
pkg/metrics/metrics_test.go

@@ -19,8 +19,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")