metrics: fix resetting timer

This commit is contained in:
Martin Holst Swende 2024-11-27 05:08:45 +01:00
parent 4111636841
commit 9e22de7c47
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
5 changed files with 31 additions and 68 deletions

View file

@ -174,7 +174,7 @@ func (exp *exp) publishTimer(name string, metric *metrics.Timer) {
exp.getFloat(name + ".mean-rate").Set(t.RateMean()) exp.getFloat(name + ".mean-rate").Set(t.RateMean())
} }
func (exp *exp) publishResettingTimer(name string, metric metrics.ResettingTimer) { func (exp *exp) publishResettingTimer(name string, metric *metrics.ResettingTimer) {
t := metric.Snapshot() t := metric.Snapshot()
ps := t.Percentiles([]float64{0.50, 0.75, 0.95, 0.99}) ps := t.Percentiles([]float64{0.50, 0.75, 0.95, 0.99})
exp.getInt(name + ".count").Set(int64(t.Count())) exp.getInt(name + ".count").Set(int64(t.Count()))
@ -204,7 +204,7 @@ func (exp *exp) syncToExpvar() {
exp.publishMeter(name, i) exp.publishMeter(name, i)
case *metrics.Timer: case *metrics.Timer:
exp.publishTimer(name, i) exp.publishTimer(name, i)
case metrics.ResettingTimer: case *metrics.ResettingTimer:
exp.publishResettingTimer(name, i) exp.publishResettingTimer(name, i)
default: default:
panic(fmt.Sprintf("unsupported type for '%s': %T", name, i)) panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))

View file

@ -97,7 +97,7 @@ func readMeter(namespace, name string, i interface{}) (string, map[string]interf
"meanrate": ms.RateMean(), "meanrate": ms.RateMean(),
} }
return measurement, fields return measurement, fields
case metrics.ResettingTimer: case *metrics.ResettingTimer:
ms := metric.Snapshot() ms := metric.Snapshot()
if ms.Count() == 0 { if ms.Count() == 0 {
break break

View file

@ -67,7 +67,7 @@ func (c *collector) Add(name string, i any) error {
c.addMeter(name, m.Snapshot()) c.addMeter(name, m.Snapshot())
case *metrics.Timer: case *metrics.Timer:
c.addTimer(name, m.Snapshot()) c.addTimer(name, m.Snapshot())
case metrics.ResettingTimer: case *metrics.ResettingTimer:
c.addResettingTimer(name, m.Snapshot()) c.addResettingTimer(name, m.Snapshot())
default: default:
return fmt.Errorf("unknown prometheus metric type %T", i) return fmt.Errorf("unknown prometheus metric type %T", i)
@ -121,7 +121,7 @@ func (c *collector) addTimer(name string, m *metrics.TimerSnapshot) {
c.buff.WriteRune('\n') c.buff.WriteRune('\n')
} }
func (c *collector) addResettingTimer(name string, m metrics.ResettingTimerSnapshot) { func (c *collector) addResettingTimer(name string, m *metrics.ResettingTimerSnapshot) {
if m.Count() <= 0 { if m.Count() <= 0 {
return return
} }

View file

@ -211,7 +211,7 @@ func (r *StandardRegistry) Unregister(name string) {
func (r *StandardRegistry) loadOrRegister(name string, i interface{}) (interface{}, bool, bool) { func (r *StandardRegistry) loadOrRegister(name string, i interface{}) (interface{}, bool, bool) {
switch i.(type) { switch i.(type) {
case *Counter, *CounterFloat64, *Gauge, *GaugeFloat64, *GaugeInfo, *Healthcheck, Histogram, *Meter, *Timer, ResettingTimer: case *Counter, *CounterFloat64, *Gauge, *GaugeFloat64, *GaugeInfo, *Healthcheck, Histogram, *Meter, *Timer, *ResettingTimer:
default: default:
return nil, false, false return nil, false, false
} }

View file

@ -5,36 +5,17 @@ import (
"time" "time"
) )
// Initial slice capacity for the values stored in a ResettingTimer
const InitialResettingTimerSliceCap = 10
type ResettingTimerSnapshot interface {
Count() int
Mean() float64
Max() int64
Min() int64
Percentiles([]float64) []float64
}
// ResettingTimer is used for storing aggregated values for timers, which are reset on every flush interval.
type ResettingTimer interface {
Snapshot() ResettingTimerSnapshot
Time(func())
Update(time.Duration)
UpdateSince(time.Time)
}
// GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a // GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a
// new StandardResettingTimer. // new ResettingTimer.
func GetOrRegisterResettingTimer(name string, r Registry) ResettingTimer { func GetOrRegisterResettingTimer(name string, r Registry) *ResettingTimer {
if nil == r { if nil == r {
r = DefaultRegistry r = DefaultRegistry
} }
return r.GetOrRegister(name, NewResettingTimer).(ResettingTimer) return r.GetOrRegister(name, NewResettingTimer).(*ResettingTimer)
} }
// NewRegisteredResettingTimer constructs and registers a new StandardResettingTimer. // NewRegisteredResettingTimer constructs and registers a new ResettingTimer.
func NewRegisteredResettingTimer(name string, r Registry) ResettingTimer { func NewRegisteredResettingTimer(name string, r Registry) *ResettingTimer {
c := NewResettingTimer() c := NewResettingTimer()
if nil == r { if nil == r {
r = DefaultRegistry r = DefaultRegistry
@ -43,33 +24,15 @@ func NewRegisteredResettingTimer(name string, r Registry) ResettingTimer {
return c return c
} }
// NewResettingTimer constructs a new StandardResettingTimer // NewResettingTimer constructs a new ResettingTimer
func NewResettingTimer() ResettingTimer { func NewResettingTimer() *ResettingTimer {
if !metricsEnabled { return &ResettingTimer{
return NilResettingTimer{} values: make([]int64, 0, 10),
}
return &StandardResettingTimer{
values: make([]int64, 0, InitialResettingTimerSliceCap),
} }
} }
// NilResettingTimer is a no-op ResettingTimer. // ResettingTimer is used for storing aggregated values for timers, which are reset on every flush interval.
type NilResettingTimer struct{} type ResettingTimer struct {
func (NilResettingTimer) Values() []int64 { return nil }
func (n NilResettingTimer) Snapshot() ResettingTimerSnapshot { return n }
func (NilResettingTimer) Time(f func()) { f() }
func (NilResettingTimer) Update(time.Duration) {}
func (NilResettingTimer) Percentiles([]float64) []float64 { return nil }
func (NilResettingTimer) Mean() float64 { return 0.0 }
func (NilResettingTimer) Max() int64 { return 0 }
func (NilResettingTimer) Min() int64 { return 0 }
func (NilResettingTimer) UpdateSince(time.Time) {}
func (NilResettingTimer) Count() int { return 0 }
// StandardResettingTimer is the standard implementation of a ResettingTimer.
// and Meter.
type StandardResettingTimer struct {
values []int64 values []int64
sum int64 // sum is a running count of the total sum, used later to calculate mean sum int64 // sum is a running count of the total sum, used later to calculate mean
@ -77,28 +40,28 @@ type StandardResettingTimer struct {
} }
// Snapshot resets the timer and returns a read-only copy of its contents. // Snapshot resets the timer and returns a read-only copy of its contents.
func (t *StandardResettingTimer) Snapshot() ResettingTimerSnapshot { func (t *ResettingTimer) Snapshot() *ResettingTimerSnapshot {
t.mutex.Lock() t.mutex.Lock()
defer t.mutex.Unlock() defer t.mutex.Unlock()
snapshot := &resettingTimerSnapshot{} snapshot := &ResettingTimerSnapshot{}
if len(t.values) > 0 { if len(t.values) > 0 {
snapshot.mean = float64(t.sum) / float64(len(t.values)) snapshot.mean = float64(t.sum) / float64(len(t.values))
snapshot.values = t.values snapshot.values = t.values
t.values = make([]int64, 0, InitialResettingTimerSliceCap) t.values = make([]int64, 0, 10)
} }
t.sum = 0 t.sum = 0
return snapshot return snapshot
} }
// Record the duration of the execution of the given function. // Record the duration of the execution of the given function.
func (t *StandardResettingTimer) Time(f func()) { func (t *ResettingTimer) Time(f func()) {
ts := time.Now() ts := time.Now()
f() f()
t.Update(time.Since(ts)) t.Update(time.Since(ts))
} }
// Record the duration of an event. // Record the duration of an event.
func (t *StandardResettingTimer) Update(d time.Duration) { func (t *ResettingTimer) Update(d time.Duration) {
t.mutex.Lock() t.mutex.Lock()
defer t.mutex.Unlock() defer t.mutex.Unlock()
t.values = append(t.values, int64(d)) t.values = append(t.values, int64(d))
@ -106,12 +69,12 @@ func (t *StandardResettingTimer) Update(d time.Duration) {
} }
// Record the duration of an event that started at a time and ends now. // Record the duration of an event that started at a time and ends now.
func (t *StandardResettingTimer) UpdateSince(ts time.Time) { func (t *ResettingTimer) UpdateSince(ts time.Time) {
t.Update(time.Since(ts)) t.Update(time.Since(ts))
} }
// resettingTimerSnapshot is a point-in-time copy of another ResettingTimer. // ResettingTimerSnapshot is a point-in-time copy of another ResettingTimer.
type resettingTimerSnapshot struct { type ResettingTimerSnapshot struct {
values []int64 values []int64
mean float64 mean float64
max int64 max int64
@ -121,20 +84,20 @@ type resettingTimerSnapshot struct {
} }
// Count return the length of the values from snapshot. // Count return the length of the values from snapshot.
func (t *resettingTimerSnapshot) Count() int { func (t *ResettingTimerSnapshot) Count() int {
return len(t.values) return len(t.values)
} }
// Percentiles returns the boundaries for the input percentiles. // Percentiles returns the boundaries for the input percentiles.
// note: this method is not thread safe // note: this method is not thread safe
func (t *resettingTimerSnapshot) Percentiles(percentiles []float64) []float64 { func (t *ResettingTimerSnapshot) Percentiles(percentiles []float64) []float64 {
t.calc(percentiles) t.calc(percentiles)
return t.thresholdBoundaries return t.thresholdBoundaries
} }
// Mean returns the mean of the snapshotted values // Mean returns the mean of the snapshotted values
// note: this method is not thread safe // note: this method is not thread safe
func (t *resettingTimerSnapshot) Mean() float64 { func (t *ResettingTimerSnapshot) Mean() float64 {
if !t.calculated { if !t.calculated {
t.calc(nil) t.calc(nil)
} }
@ -144,7 +107,7 @@ func (t *resettingTimerSnapshot) Mean() float64 {
// Max returns the max of the snapshotted values // Max returns the max of the snapshotted values
// note: this method is not thread safe // note: this method is not thread safe
func (t *resettingTimerSnapshot) Max() int64 { func (t *ResettingTimerSnapshot) Max() int64 {
if !t.calculated { if !t.calculated {
t.calc(nil) t.calc(nil)
} }
@ -153,14 +116,14 @@ func (t *resettingTimerSnapshot) Max() int64 {
// Min returns the min of the snapshotted values // Min returns the min of the snapshotted values
// note: this method is not thread safe // note: this method is not thread safe
func (t *resettingTimerSnapshot) Min() int64 { func (t *ResettingTimerSnapshot) Min() int64 {
if !t.calculated { if !t.calculated {
t.calc(nil) t.calc(nil)
} }
return t.min return t.min
} }
func (t *resettingTimerSnapshot) calc(percentiles []float64) { func (t *ResettingTimerSnapshot) calc(percentiles []float64) {
scores := CalculatePercentiles(t.values, percentiles) scores := CalculatePercentiles(t.values, percentiles)
t.thresholdBoundaries = scores t.thresholdBoundaries = scores
if len(t.values) == 0 { if len(t.values) == 0 {