ソースを参照

Add GetOrRegister method for threadsafe metric registration

Advantages over existing metric creation and registration:

* A single statement creates and registers a metric.
* Unused metrics will now be a compiler error (previously all metrics
  would be "used" at least once when registering them).
* The only threadsafe way to dynamically get-or-create metrics without
  wrapping the existing interface.
* Able to get/create metrics locally instead using global variables.
Michael Schurter 12 年 前
コミット
794f69380a
2 ファイル変更46 行追加0 行削除
  1. 21 0
      registry.go
  2. 25 0
      registry_test.go

+ 21 - 0
registry.go

@@ -18,6 +18,9 @@ type Registry interface {
 	// Register the given metric under the given name.
 	Register(string, interface{})
 
+	// Gets an existing metric or creates and registers a new one.
+	GetOrRegister(string, interface{}) interface{}
+
 	// Run all registered healthchecks.
 	RunHealthchecks()
 
@@ -61,6 +64,24 @@ func (r *StandardRegistry) Register(name string, i interface{}) {
 	}
 }
 
+// Gets an existing metric or creates and registers a new one. Threadsafe
+// alternative to calling Get and Register on failure.
+func (r *StandardRegistry) GetOrRegister(name string, i interface{}) interface{} {
+	r.mutex.Lock()
+	defer r.mutex.Unlock()
+	if metric, ok := r.metrics[name]; ok {
+		// Found existing metric, return it instead of the new instance
+		return metric
+	}
+
+	// No existing metric, register the new instance
+	switch i.(type) {
+	case Counter, Gauge, Healthcheck, Histogram, Meter, Timer:
+		r.metrics[name] = i
+	}
+	return i
+}
+
 // Run all registered healthchecks.
 func (r *StandardRegistry) RunHealthchecks() {
 	r.mutex.Lock()

+ 25 - 0
registry_test.go

@@ -34,3 +34,28 @@ func TestRegistry(t *testing.T) {
 		t.Fatal(i)
 	}
 }
+
+func TestGetOrRegister(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)
+	}
+}