Skip to content

Commit d5a250f

Browse files
authored
[monitoring] Add ClearOnVisitHistogram to adapter for go-metrics (#330)
Add ClearOnVisitHistogram to adapter for go-metrics This type is the same as go-metrics Histogram except that it is cleared (counts reset) every time it is Visited (read for a report).
1 parent 531c756 commit d5a250f

File tree

4 files changed

+341
-30
lines changed

4 files changed

+341
-30
lines changed

monitoring/adapter/go-metrics-wrapper.go

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525

2626
// go-metrics wrapper interface required to unpack the original metric
2727
type goMetricsWrapper interface {
28-
wrapped() interface{}
28+
wrapped() any
2929
}
3030

3131
// go-metrics wrappers
@@ -40,13 +40,14 @@ type (
4040
g metrics.FunctionalGaugeFloat64
4141
}
4242

43-
goMetricsHistogram struct{ h metrics.Histogram }
43+
goMetricsHistogram struct{ h metrics.Histogram }
44+
goMetricsClearOnVisitHistogram struct{ h *ClearOnVisitHistogram }
4445

4546
goMetricsMeter struct{ m metrics.Meter }
4647
)
4748

4849
// goMetricsWrap tries to wrap a metric for use with monitoring package.
49-
func goMetricsWrap(metric interface{}) (monitoring.Var, bool) {
50+
func goMetricsWrap(metric any) (monitoring.Var, bool) {
5051
switch v := metric.(type) {
5152
case *metrics.StandardCounter:
5253
return goMetricsCounter{v}, true
@@ -62,42 +63,44 @@ func goMetricsWrap(metric interface{}) (monitoring.Var, bool) {
6263
return goMetricsHistogram{v}, true
6364
case *metrics.StandardMeter:
6465
return goMetricsMeter{v}, true
66+
case *ClearOnVisitHistogram:
67+
return goMetricsClearOnVisitHistogram{v}, true
6568
}
6669
return nil, false
6770
}
6871

69-
func (w goMetricsCounter) wrapped() interface{} { return w.c }
70-
func (w goMetricsCounter) Get() int64 { return w.c.Count() }
72+
func (w goMetricsCounter) wrapped() any { return w.c }
73+
func (w goMetricsCounter) Get() int64 { return w.c.Count() }
7174
func (w goMetricsCounter) Visit(_ monitoring.Mode, vs monitoring.Visitor) {
7275
vs.OnInt(w.Get())
7376
}
7477

75-
func (w goMetricsGauge) wrapped() interface{} { return w.g }
76-
func (w goMetricsGauge) Get() int64 { return w.g.Value() }
78+
func (w goMetricsGauge) wrapped() any { return w.g }
79+
func (w goMetricsGauge) Get() int64 { return w.g.Value() }
7780
func (w goMetricsGauge) Visit(_ monitoring.Mode, vs monitoring.Visitor) {
7881
vs.OnInt(w.Get())
7982
}
8083

81-
func (w goMetricsGaugeFloat64) wrapped() interface{} { return w.g }
82-
func (w goMetricsGaugeFloat64) Get() float64 { return w.g.Value() }
84+
func (w goMetricsGaugeFloat64) wrapped() any { return w.g }
85+
func (w goMetricsGaugeFloat64) Get() float64 { return w.g.Value() }
8386
func (w goMetricsGaugeFloat64) Visit(_ monitoring.Mode, vs monitoring.Visitor) {
8487
vs.OnFloat(w.Get())
8588
}
8689

87-
func (w goMetricsFuncGauge) wrapped() interface{} { return w.g }
88-
func (w goMetricsFuncGauge) Get() int64 { return w.g.Value() }
90+
func (w goMetricsFuncGauge) wrapped() any { return w.g }
91+
func (w goMetricsFuncGauge) Get() int64 { return w.g.Value() }
8992
func (w goMetricsFuncGauge) Visit(_ monitoring.Mode, vs monitoring.Visitor) {
9093
vs.OnInt(w.Get())
9194
}
9295

93-
func (w goMetricsFuncGaugeFloat) wrapped() interface{} { return w.g }
94-
func (w goMetricsFuncGaugeFloat) Get() float64 { return w.g.Value() }
96+
func (w goMetricsFuncGaugeFloat) wrapped() any { return w.g }
97+
func (w goMetricsFuncGaugeFloat) Get() float64 { return w.g.Value() }
9598
func (w goMetricsFuncGaugeFloat) Visit(_ monitoring.Mode, vs monitoring.Visitor) {
9699
vs.OnFloat(w.Get())
97100
}
98101

99-
func (w goMetricsHistogram) wrapped() interface{} { return w.h }
100-
func (w goMetricsHistogram) Get() int64 { return w.h.Sum() }
102+
func (w goMetricsHistogram) wrapped() any { return w.h }
103+
func (w goMetricsHistogram) Get() int64 { return w.h.Sum() }
101104
func (w goMetricsHistogram) Visit(_ monitoring.Mode, vs monitoring.Visitor) {
102105
vs.OnRegistryStart()
103106
defer vs.OnRegistryFinished()
@@ -126,8 +129,30 @@ func (w goMetricsHistogram) Visit(_ monitoring.Mode, vs monitoring.Visitor) {
126129
vs.OnFloat(ps[4])
127130
}
128131

129-
func (w goMetricsMeter) wrapped() interface{} { return w.m }
130-
func (w goMetricsMeter) Get() int64 { return w.m.Count() }
132+
func (w goMetricsMeter) wrapped() any { return w.m }
133+
func (w goMetricsMeter) Get() int64 { return w.m.Count() }
131134
func (w goMetricsMeter) Visit(_ monitoring.Mode, vs monitoring.Visitor) {
132135
vs.OnInt(w.Get())
133136
}
137+
138+
func (w goMetricsClearOnVisitHistogram) wrapped() any { return w.h }
139+
func (w goMetricsClearOnVisitHistogram) Get() int64 { return w.h.Sum() }
140+
func (w goMetricsClearOnVisitHistogram) Visit(_ monitoring.Mode, vs monitoring.Visitor) {
141+
vs.OnRegistryStart()
142+
defer vs.OnRegistryFinished()
143+
144+
h := w.h.Snapshot()
145+
w.h.Clear()
146+
ps := h.Percentiles([]float64{0.5, 0.99})
147+
vs.OnKey("count")
148+
vs.OnInt(h.Count())
149+
vs.OnKey("min")
150+
vs.OnInt(h.Min())
151+
vs.OnKey("max")
152+
vs.OnInt(h.Max())
153+
vs.OnKey("median")
154+
vs.OnFloat(ps[0])
155+
vs.OnKey("p99")
156+
vs.OnFloat(ps[1])
157+
158+
}

monitoring/adapter/go-metrics.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ import (
3636
// the go-metrics.Registry interface.
3737
//
3838
// Note: with the go-metrics using `interface{}`, there is no guarantee
39-
// a variable satisfying any of go-metrics interfaces is returned.
40-
// It's recommended to not mix go-metrics with other metrics types
41-
// in the same namespace.
39+
//
40+
// a variable satisfying any of go-metrics interfaces is returned.
41+
// It's recommended to not mix go-metrics with other metrics types
42+
// in the same namespace.
4243
type GoMetricsRegistry struct {
4344
mutex sync.Mutex
4445

@@ -54,14 +55,15 @@ type GoMetricsRegistry struct {
5455
// If the monitoring.Registry does not exist yet, a new one will be generated.
5556
//
5657
// Note: with users of go-metrics potentially removing any metric at runtime,
57-
// it's recommended to have the underlying registry being generated with
58-
// `monitoring.IgnorePublishExpvar`.
58+
//
59+
// it's recommended to have the underlying registry being generated with
60+
// `monitoring.IgnorePublishExpvar`.
5961
func GetGoMetrics(parent *monitoring.Registry, name string, filters ...MetricFilter) *GoMetricsRegistry {
6062
v := parent.Get(name)
6163
if v == nil {
6264
return NewGoMetrics(parent, name, filters...)
6365
}
64-
return newGoMetrics(v.(*monitoring.Registry), filters...)
66+
return newGoMetrics(v.(*monitoring.Registry), filters...) //nolint:errcheck //code depends on panic
6567
}
6668

6769
// NewGoMetrics creates and registers a new GoMetricsRegistry with the parent
@@ -97,9 +99,10 @@ func (r *GoMetricsRegistry) find(name string) interface{} {
9799
// Get retrieves a registered metric by name. If the name is unknown, Get returns nil.
98100
//
99101
// Note: with the return values being `interface{}`, there is no guarantee
100-
// a variable satisfying any of go-metrics interfaces is returned.
101-
// It's recommended to not mix go-metrics with other metrics types in one
102-
// namespace.
102+
//
103+
// a variable satisfying any of go-metrics interfaces is returned.
104+
// It's recommended to not mix go-metrics with other metrics types in one
105+
// namespace.
103106
func (r *GoMetricsRegistry) Get(name string) interface{} {
104107
r.mutex.Lock()
105108
defer r.mutex.Unlock()

monitoring/adapter/go-metrics_test.go

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ func TestGoMetricsAdapter(t *testing.T) {
5858
// register some metrics and check they're satisfying the go-metrics interface
5959
// no matter if owned by monitoring or go-metrics
6060
for name := range counters {
61-
cnt, ok := reg.GetOrRegister(name, func() interface{} {
61+
cnt, ok := reg.GetOrRegister(name, func() any {
6262
return metrics.NewCounter()
6363
}).(metrics.Counter)
6464
require.True(t, ok)
6565
cnt.Clear()
6666
}
6767

6868
for name := range meters {
69-
meter, ok := reg.GetOrRegister(name, func() interface{} {
69+
meter, ok := reg.GetOrRegister(name, func() any {
7070
return metrics.NewMeter()
7171
}).(metrics.Meter)
7272
require.True(t, ok)
@@ -100,14 +100,117 @@ func TestGoMetricsAdapter(t *testing.T) {
100100
}
101101

102102
// check Each only returns metrics not registered with monitoring.Registry
103-
reg.Each(func(name string, v interface{}) {
103+
reg.Each(func(name string, v any) {
104104
if strings.HasPrefix(name, "mon") {
105105
t.Errorf("metric %v should not have been reported by each", name)
106106
}
107107
})
108-
monReg.Do(monitoring.Full, func(name string, v interface{}) {
108+
monReg.Do(monitoring.Full, func(name string, v any) {
109109
if !strings.HasPrefix(name, "test.mon") {
110110
t.Errorf("metric %v should not have been reported by each", name)
111111
}
112112
})
113113
}
114+
115+
func TestGoMetricsHistogramClearOnVisit(t *testing.T) {
116+
monReg := monitoring.NewRegistry()
117+
histogramSample := metrics.NewUniformSample(10)
118+
clearedHistogramSample := metrics.NewUniformSample(10)
119+
_ = NewGoMetrics(monReg, "original", Accept).Register("histogram", metrics.NewHistogram(histogramSample))
120+
_ = NewGoMetrics(monReg, "cleared", Accept).Register("histogram", NewClearOnVisitHistogram(clearedHistogramSample))
121+
dataPoints := [...]int{2, 4, 8, 4, 2}
122+
dataPointsMedian := 4.0
123+
for _, i := range dataPoints {
124+
histogramSample.Update(int64(i))
125+
clearedHistogramSample.Update(int64(i))
126+
}
127+
128+
preSnapshot := []struct {
129+
expected any
130+
actual any
131+
msg string
132+
}{
133+
{
134+
actual: histogramSample.Count(),
135+
expected: int64(len(dataPoints)),
136+
msg: "histogram sample count incorrect",
137+
},
138+
{
139+
actual: clearedHistogramSample.Count(),
140+
expected: int64(len(dataPoints)),
141+
msg: "cleared histogram sample count incorrect",
142+
},
143+
{
144+
actual: histogramSample.Percentiles([]float64{0.5}),
145+
expected: []float64{dataPointsMedian},
146+
msg: "histogram median incorrect",
147+
},
148+
{
149+
actual: clearedHistogramSample.Percentiles([]float64{0.5}),
150+
expected: []float64{dataPointsMedian},
151+
msg: "cleared histogram median incorrect",
152+
},
153+
}
154+
155+
for _, tc := range preSnapshot {
156+
require.Equal(t, tc.expected, tc.actual, tc.msg)
157+
}
158+
159+
// collecting the snapshot triggers the visit of each histogram
160+
flatSnapshot := monitoring.CollectFlatSnapshot(monReg, monitoring.Full, false)
161+
162+
// Check to make sure after the snapshot the samples in
163+
// clearedHistogramSample have been reset to zero, but the
164+
// snapshot reports the values before the clear
165+
166+
postSnapshot := []struct {
167+
expected any
168+
actual any
169+
msg string
170+
}{
171+
{
172+
actual: histogramSample.Count(),
173+
expected: int64(len(dataPoints)),
174+
msg: "histogram sample count incorrect",
175+
},
176+
{
177+
actual: clearedHistogramSample.Count(),
178+
expected: int64(0),
179+
msg: "cleared histogram sample count incorrect",
180+
},
181+
{
182+
actual: histogramSample.Percentiles([]float64{0.5}),
183+
expected: []float64{dataPointsMedian},
184+
msg: "histogram median incorrect",
185+
},
186+
{
187+
actual: clearedHistogramSample.Percentiles([]float64{0.5}),
188+
expected: []float64{0},
189+
msg: "cleared histogram median incorrect",
190+
},
191+
{
192+
actual: flatSnapshot.Ints["original.histogram.count"],
193+
expected: int64(len(dataPoints)),
194+
msg: "visited histogram count is wrong",
195+
},
196+
{
197+
actual: flatSnapshot.Ints["cleared.histogram.count"],
198+
expected: int64(len(dataPoints)),
199+
msg: "visited cleared histogram count is wrong",
200+
},
201+
{
202+
actual: flatSnapshot.Floats["original.histogram.median"],
203+
expected: float64(dataPointsMedian),
204+
msg: "visited histogram median is wrong",
205+
},
206+
{
207+
actual: flatSnapshot.Floats["cleared.histogram.median"],
208+
expected: float64(dataPointsMedian),
209+
msg: "visited cleared histogram median is wrong",
210+
},
211+
}
212+
213+
for _, tc := range postSnapshot {
214+
require.Equal(t, tc.expected, tc.actual, tc.msg)
215+
}
216+
}

0 commit comments

Comments
 (0)