Added GetOrCreateSummary*
This commit is contained in:
parent
fd3b1c9ee6
commit
bf93e5d810
@ -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))
|
||||||
|
@ -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)
|
||||||
|
112
summary.go
112
summary.go
@ -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 {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user