Added GetOrCreateSummary*

This commit is contained in:
Aliaksandr Valialkin 2019-04-11 14:03:30 +03:00
parent fd3b1c9ee6
commit bf93e5d810
4 changed files with 165 additions and 7 deletions

View File

@ -96,7 +96,6 @@ func GetOrCreateCounter(name string) *Counter {
} }
metricsMapLock.Unlock() metricsMapLock.Unlock()
} }
c, ok := nm.metric.(*Counter) c, ok := nm.metric.(*Counter)
if !ok { if !ok {
panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric)) panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric))

View File

@ -11,9 +11,10 @@ func TestInvalidName(t *testing.T) {
f := func(name string) { f := func(name string) {
t.Helper() t.Helper()
expectPanic(t, fmt.Sprintf("NewCounter(%q)", name), func() { NewCounter(name) }) expectPanic(t, fmt.Sprintf("NewCounter(%q)", name), func() { NewCounter(name) })
expectPanic(t, fmt.Sprintf("GetOrCreateCounter(%q)", name), func() { GetOrCreateCounter(name) })
expectPanic(t, fmt.Sprintf("NewGauge(%q)", name), func() { NewGauge(name, func() float64 { return 0 }) }) 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("NewSummary(%q)", name), func() { NewSummary(name) })
expectPanic(t, fmt.Sprintf("GetOrCreateCounter(%q)", name), func() { GetOrCreateCounter(name) })
expectPanic(t, fmt.Sprintf("GetOrCreateSummary(%q)", name), func() { GetOrCreateSummary(name) })
} }
f("") f("")
f("foo{") f("foo{")
@ -51,6 +52,12 @@ func TestGetOrCreateNotCounter(t *testing.T) {
expectPanic(t, name, func() { GetOrCreateCounter(name) }) expectPanic(t, name, func() { GetOrCreateCounter(name) })
} }
func TestGetOrCreateNotSummary(t *testing.T) {
name := "GetOrCreateNotSummary"
NewCounter(name)
expectPanic(t, name, func() { GetOrCreateSummary(name) })
}
func TestWritePrometheusSerial(t *testing.T) { func TestWritePrometheusSerial(t *testing.T) {
if err := testWritePrometheus(); err != nil { if err := testWritePrometheus(); err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -23,6 +23,8 @@ type Summary struct {
quantiles []float64 quantiles []float64
quantileValues []float64 quantileValues []float64
window time.Duration
} }
// NewSummary creates and returns new summary with the given name. // NewSummary creates and returns new summary with the given name.
@ -51,16 +53,29 @@ func NewSummary(name string) *Summary {
// //
// The returned summary is safe to use from concurrent goroutines. // The returned summary is safe to use from concurrent goroutines.
func NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary { func NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
s := newSummary(window, quantiles)
registerMetric(name, s)
registerSummary(s)
registerSummaryQuantiles(name, s)
return s
}
func newSummary(window time.Duration, quantiles []float64) *Summary {
// Make a copy of quantiles in order to prevent from their modification by the caller.
quantiles = append([]float64{}, quantiles...)
validateQuantiles(quantiles) validateQuantiles(quantiles)
s := &Summary{ s := &Summary{
curr: histogram.NewFast(), curr: histogram.NewFast(),
next: histogram.NewFast(), next: histogram.NewFast(),
quantiles: quantiles, quantiles: quantiles,
quantileValues: make([]float64, len(quantiles)), quantileValues: make([]float64, len(quantiles)),
window: window,
} }
registerSummary(s, window) return s
registerMetric(name, s) }
for i, q := range quantiles {
func registerSummaryQuantiles(name string, s *Summary) {
for i, q := range s.quantiles {
quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q)) quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q))
qv := &quantileValue{ qv := &quantileValue{
s: s, s: s,
@ -68,7 +83,6 @@ func NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summ
} }
registerMetric(quantileValueName, qv) registerMetric(quantileValueName, qv)
} }
return s
} }
func validateQuantiles(quantiles []float64) { func validateQuantiles(quantiles []float64) {
@ -105,6 +119,93 @@ func (s *Summary) updateQuantiles() {
s.mu.Unlock() s.mu.Unlock()
} }
// GetOrCreateSummary returns registered summary with the given name
// or creates new summary if the registry doesn't contain summary 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 summary is safe to use from concurrent goroutines.
//
// Performance tip: prefer NewSummary instead of GetOrCreateSummary.
func GetOrCreateSummary(name string) *Summary {
return GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
}
// GetOrCreateSummaryExt returns registered summary with the given name,
// window and quantiles or creates new summary if the registry doesn't
// contain summary 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 summary is safe to use from concurrent goroutines.
//
// Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt.
func GetOrCreateSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
metricsMapLock.Lock()
nm := metricsMap[name]
metricsMapLock.Unlock()
if nm == nil {
// Slow path - create and register missing summary.
if err := validateMetric(name); err != nil {
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
}
s := newSummary(window, quantiles)
nmNew := &namedMetric{
name: name,
metric: s,
}
mustRegisterQuantiles := false
metricsMapLock.Lock()
nm = metricsMap[name]
if nm == nil {
nm = nmNew
metricsMap[name] = nm
metricsList = append(metricsList, nm)
registerSummary(s)
mustRegisterQuantiles = true
}
metricsMapLock.Unlock()
if mustRegisterQuantiles {
registerSummaryQuantiles(name, s)
}
}
s, ok := nm.metric.(*Summary)
if !ok {
panic(fmt.Errorf("BUG: metric %q isn't a Summary. It is %T", name, nm.metric))
}
if s.window != window {
panic(fmt.Errorf("BUG: invalid window requested for the summary %q; requested %s; need %s", name, window, s.window))
}
if !isEqualQuantiles(s.quantiles, quantiles) {
panic(fmt.Errorf("BUG: invalid quantiles requested from the summary %q; requested %v; need %v", name, quantiles, s.quantiles))
}
return s
}
func isEqualQuantiles(a, b []float64) bool {
// Do not use relfect.DeepEqual, since it is slower than the direct comparison.
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
type quantileValue struct { type quantileValue struct {
s *Summary s *Summary
idx int idx int
@ -126,7 +227,8 @@ func addTag(name, tag string) string {
return fmt.Sprintf("%s,%s}", name[:len(name)-1], tag) return fmt.Sprintf("%s,%s}", name[:len(name)-1], tag)
} }
func registerSummary(s *Summary, window time.Duration) { func registerSummary(s *Summary) {
window := s.window
summariesLock.Lock() summariesLock.Lock()
summaries[window] = append(summaries[window], s) summaries[window] = append(summaries[window], s)
if len(summaries[window]) == 1 { if len(summaries[window]) == 1 {

View File

@ -2,6 +2,7 @@ package metrics
import ( import (
"bytes" "bytes"
"fmt"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -99,3 +100,52 @@ func TestSummarySmallWindow(t *testing.T) {
t.Fatalf("summary %s cannot be present in the WritePrometheus output; got\n%s", name, result) t.Fatalf("summary %s cannot be present in the WritePrometheus output; got\n%s", name, result)
} }
} }
func TestGetOrCreateSummaryInvalidWindow(t *testing.T) {
name := "GetOrCreateSummaryInvalidWindow"
GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
expectPanic(t, name, func() {
GetOrCreateSummaryExt(name, defaultSummaryWindow/2, defaultSummaryQuantiles)
})
}
func TestGetOrCreateSummaryInvalidQuantiles(t *testing.T) {
name := "GetOrCreateSummaryInvalidQuantiles"
GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
expectPanic(t, name, func() {
GetOrCreateSummaryExt(name, defaultSummaryWindow, []float64{0.1, 0.2})
})
quantiles := append([]float64{}, defaultSummaryQuantiles...)
quantiles[len(quantiles)-1] /= 2
expectPanic(t, name, func() {
GetOrCreateSummaryExt(name, defaultSummaryWindow, quantiles)
})
}
func TestGetOrCreateSummarySerial(t *testing.T) {
name := "GetOrCreateSummarySerial"
if err := testGetOrCreateSummary(name); err != nil {
t.Fatal(err)
}
}
func TestGetOrCreateSummaryConcurrent(t *testing.T) {
name := "GetOrCreateSummaryConcurrent"
err := testConcurrent(func() error {
return testGetOrCreateSummary(name)
})
if err != nil {
t.Fatal(err)
}
}
func testGetOrCreateSummary(name string) error {
s1 := GetOrCreateSummary(name)
for i := 0; i < 10; i++ {
s2 := GetOrCreateSummary(name)
if s1 != s2 {
return fmt.Errorf("unexpected summary returned; got %p; want %p", s2, s1)
}
}
return nil
}