diff --git a/gauge.go b/gauge.go index a50e488..a71886d 100644 --- a/gauge.go +++ b/gauge.go @@ -19,6 +19,9 @@ import ( // // The returned gauge is safe to use from concurrent goroutines. func NewGauge(name string, f func() float64) *Gauge { + if f == nil { + panic(fmt.Errorf("BUG: f cannot be nil")) + } g := &Gauge{ f: f, } @@ -47,3 +50,51 @@ func (g *Gauge) marshalTo(prefix string, w io.Writer) { fmt.Fprintf(w, "%s %g\n", prefix, v) } } + +// GetOrCreateGauge returns registered gauge with the given name +// or creates new gauge if the registry doesn't contain gauge with +// the given name. +// +// name must be valid Prometheus-compatible metric with possible lables. +// For instance, +// +// * foo +// * foo{bar="baz"} +// * foo{bar="baz",aaa="b"} +// +// The returned gauge is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewGauge instead of GetOrCreateGauge. +func GetOrCreateGauge(name string, f func() float64) *Gauge { + metricsMapLock.Lock() + nm := metricsMap[name] + metricsMapLock.Unlock() + if nm == nil { + // Slow path - create and register missing gauge. + if f == nil { + panic(fmt.Errorf("BUG: f cannot be nil")) + } + if err := validateMetric(name); err != nil { + panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) + } + nmNew := &namedMetric{ + name: name, + metric: &Gauge{ + f: f, + }, + } + metricsMapLock.Lock() + nm = metricsMap[name] + if nm == nil { + nm = nmNew + metricsMap[name] = nm + metricsList = append(metricsList, nm) + } + metricsMapLock.Unlock() + } + g, ok := nm.metric.(*Gauge) + if !ok { + panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric)) + } + return g +} diff --git a/gauge_example_test.go b/gauge_example_test.go index 56a6e47..0f0e440 100644 --- a/gauge_example_test.go +++ b/gauge_example_test.go @@ -16,3 +16,26 @@ func ExampleGauge() { // Obtain gauge value. fmt.Println(g.Get()) } + +func ExampleGauge_vec() { + for i := 0; i < 3; i++ { + // Dynamically construct metric name and pass it to GetOrCreateGauge. + name := fmt.Sprintf(`metric{label1=%q, label2="%d"}`, "value1", i) + iLocal := i + metrics.GetOrCreateGauge(name, func() float64 { + return float64(iLocal + 1) + }) + } + + // Read counter values. + for i := 0; i < 3; i++ { + name := fmt.Sprintf(`metric{label1=%q, label2="%d"}`, "value1", i) + n := metrics.GetOrCreateGauge(name, func() float64 { return 0 }).Get() + fmt.Println(n) + } + + // Output: + // 1 + // 2 + // 3 +} diff --git a/gauge_test.go b/gauge_test.go index 3bb199f..3f6e5cd 100644 --- a/gauge_test.go +++ b/gauge_test.go @@ -6,6 +6,15 @@ import ( "testing" ) +func TestGaugeError(t *testing.T) { + expectPanic(t, "NewGauge_nil_callback", func() { + NewGauge("NewGauge_nil_callback", nil) + }) + expectPanic(t, "GetOrCreateGauge_nil_callback", func() { + GetOrCreateGauge("GetOrCreateGauge_nil_callback", nil) + }) +} + func TestGaugeSerial(t *testing.T) { name := "GaugeSerial" n := 1.23 diff --git a/metrics_test.go b/metrics_test.go index 604511f..8b00bea 100644 --- a/metrics_test.go +++ b/metrics_test.go @@ -14,6 +14,7 @@ func TestInvalidName(t *testing.T) { expectPanic(t, fmt.Sprintf("NewGauge(%q)", name), func() { NewGauge(name, func() float64 { return 0 }) }) expectPanic(t, fmt.Sprintf("NewSummary(%q)", name), func() { NewSummary(name) }) expectPanic(t, fmt.Sprintf("GetOrCreateCounter(%q)", name), func() { GetOrCreateCounter(name) }) + expectPanic(t, fmt.Sprintf("GetOrCreateGauge(%q)", name), func() { GetOrCreateGauge(name, func() float64 { return 0 }) }) expectPanic(t, fmt.Sprintf("GetOrCreateSummary(%q)", name), func() { GetOrCreateSummary(name) }) } f("") @@ -52,6 +53,12 @@ func TestGetOrCreateNotCounter(t *testing.T) { expectPanic(t, name, func() { GetOrCreateCounter(name) }) } +func TestGetOrCreateNotGauge(t *testing.T) { + name := "GetOrCreateNotGauge" + NewCounter(name) + expectPanic(t, name, func() { GetOrCreateGauge(name, func() float64 { return 0 }) }) +} + func TestGetOrCreateNotSummary(t *testing.T) { name := "GetOrCreateNotSummary" NewCounter(name)