From 64cd4da9b14b99cd4c19a7193f9aaae64268e8b4 Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Fri, 8 Aug 2025 02:20:02 +0300 Subject: [PATCH 1/4] Timer helpers Signed-off-by: Vladimir Buyanov --- prometheus/timer.go | 321 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 320 insertions(+), 1 deletion(-) diff --git a/prometheus/timer.go b/prometheus/timer.go index 52344fef5..330f88487 100644 --- a/prometheus/timer.go +++ b/prometheus/timer.go @@ -13,7 +13,9 @@ package prometheus -import "time" +import ( + "time" +) // Timer is a helper type to time functions. Use NewTimer to create new // instances. @@ -79,3 +81,320 @@ func (t *Timer) ObserveDurationWithExemplar(exemplar Labels) time.Duration { } return d } + +// TimerHistogram is a thin convenience wrapper around a Prometheus Histogram +// that makes it easier to time code paths in an idiomatic Go-style: +// +// defer timer.Observe()() // <-- starts the timer and defers the stop +type TimerHistogram struct { + Histogram +} + +func NewTimerHistogram(opts HistogramOpts) *TimerHistogram { + t := &TimerHistogram{ + Histogram: NewHistogram(opts), + } + return t +} + +// Observe starts a prom.Timer that records into the embedded Histogram and +// returns a “stop” callback. Best used with defer: +// +// defer timer.Observe()() +// +// The inner closure calls ObserveDuration on the hidden prom.Timer, recording +// the elapsed seconds into the histogram’s current bucket. +func (t *TimerHistogram) Observe() func() { + timer := NewTimer(t.Histogram) + return func() { + timer.ObserveDuration() + } +} + +// Wrap executes fn() and records the time it took. Equivalent to: +// Use when you don't need a defer chain (e.g., inside small helpers). +func (t *TimerHistogram) Wrap(fn func()) { + defer t.Observe()() + fn() +} + +type TimerHistogramVec struct { + *HistogramVec +} + +func NewTimerHistogramVec(opts HistogramOpts, labelNames []string) *TimerHistogramVec { + t := &TimerHistogramVec{ + HistogramVec: NewHistogramVec(opts, labelNames), + } + + return t +} + +// Observe return func for stop timer and observe value +// Example +// defer metric.Observe(map[string]string{"foo": "bar"})() +func (t *TimerHistogramVec) Observe(labels map[string]string) func() { + timeStart := time.Now() + return func() { + d := time.Since(timeStart) + t.HistogramVec.With(labels).Observe(d.Seconds()) + } +} + +func (t *TimerHistogramVec) ObserveLabelValues(values ...string) func() { + timeStart := time.Now() + return func() { + d := time.Since(timeStart) + t.HistogramVec.WithLabelValues(values...).Observe(d.Seconds()) + } +} + +func (t *TimerHistogramVec) Wrap(labels map[string]string, fn func()) { + defer t.Observe(labels)() + fn() +} + +func (t *TimerHistogramVec) WrapLV(values []string, fn func()) { + defer t.ObserveLabelValues(values...)() + fn() +} + +// TimerCounter is a minimal helper that turns a Prometheus **Counter** into a +// “stop-watch” for wall-clock time. +// +// Each call to Observe() starts a timer and, when the returned closure is +// executed, adds the elapsed seconds to the embedded Counter. The counter +// therefore represents **the cumulative running time** across many code paths +// (e.g. total time spent processing all requests since process start). +// +// Compared with a Histogram-based timer you gain: +// +// - A single monotonically-increasing number that is cheap to aggregate or +// alert on (e.g. “CPU-seconds spent in GC”). +// - Zero bucket management or percentile math. +// +// But you lose per-request latency data, so use it when you care about total +// time rather than distribution. +type TimerCounter struct { + Counter +} + +func NewTimerCounter(opts Opts) *TimerCounter { + t := &TimerCounter{ + Counter: NewCounter(CounterOpts(opts)), + } + + return t +} + +// Observe starts a wall-clock timer and returns a “stop” closure. +// +// Typical usage: +// +// defer myCounter.Observe()() // records on function exit +// +// When the closure is executed it records the elapsed duration (in seconds) +// into the Counter. Thread-safe as long as the underlying Counter is +// thread-safe (Prometheus counters are). +func (t *TimerCounter) Observe() func() { + start := time.Now() + + return func() { + d := time.Since(start) + t.Counter.Add(d.Seconds()) + } +} + +func (t *TimerCounter) Wrap(fn func()) { + defer t.Observe()() + fn() +} + +type TimerCounterVec struct { + *CounterVec +} + +func NewTimerCounterVec(opts Opts, labels []string) *TimerCounterVec { + t := &TimerCounterVec{ + CounterVec: NewCounterVec(CounterOpts(opts), labels), + } + + return t +} + +func (t *TimerCounterVec) Observe(labels map[string]string) func() { + start := time.Now() + + return func() { + d := time.Since(start) + t.CounterVec.With(labels).Add(d.Seconds()) + } +} + +func (t *TimerCounterVec) ObserveLabelValues(values ...string) func() { + start := time.Now() + return func() { + d := time.Since(start) + t.CounterVec.WithLabelValues(values...).Add(d.Seconds()) + } +} + +func (t *TimerCounterVec) Wrap(labels map[string]string, fn func()) { + defer t.Observe(labels)() + fn() +} + +func (t *TimerCounterVec) WrapLabelValues(values []string, fn func()) { + defer t.ObserveLabelValues(values...)() + fn() +} + +// TimerContinuous is a variant of the standard Timer that **continuously updates** +// its underlying Counter while it is running—by default once every second— +// instead of emitting a single measurement only when the timer stops. +// +// Trade-offs +// ---------- +// - **Higher overhead** than a one-shot Timer (extra goroutine + ticker). +// - **Finer-grained metrics** that are invaluable for long-running or +// indeterminate-length activities such as stream processing, background +// jobs, or large file transfers. +// - **Sensitive to clock skew**—if the system clock is moved **backwards** +// while the timer is running, the negative delta is silently discarded +// (no panic), so that slice of time is lost from the measurement. +type TimerContinuous struct { + Counter + updateInterval time.Duration +} + +func NewTimerContinuous(opts Opts, updateInterval time.Duration) *TimerContinuous { + t := &TimerContinuous{ + Counter: NewCounter(CounterOpts(opts)), + updateInterval: updateInterval, + } + if t.updateInterval == 0 { + t.updateInterval = time.Second + } + + return t +} + +func (t *TimerContinuous) Observe() func() { + start := time.Now() + ch := make(chan struct{}) + + go func() { + added := float64(0) + ticker := time.NewTicker(t.updateInterval) + defer ticker.Stop() + for { + select { + case <-ch: + d := time.Since(start) + if diff := d.Seconds() - added; diff > 0 { + t.Counter.Add(diff) + } + return + case <-ticker.C: + d := time.Since(start) + if diff := d.Seconds() - added; diff > 0 { + t.Counter.Add(diff) + added += diff + } + } + } + }() + + return func() { + ch <- struct{}{} + } +} + +func (t *TimerContinuous) Wrap(fn func()) { + defer t.Observe()() + fn() +} + +type TimerContinuousVec struct { + *CounterVec +} + +func NewTimerContinuousVec(opts Opts, labels []string) *TimerCounterVec { + t := &TimerCounterVec{ + CounterVec: NewCounterVec(CounterOpts(opts), labels), + } + + return t +} + +func (t *TimerContinuousVec) Observe(labels map[string]string) func() { + start := time.Now() + ch := make(chan struct{}) + + go func() { + added := float64(0) + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ch: + d := time.Since(start) + if diff := d.Seconds() - added; diff > 0 { + t.CounterVec.With(labels).Add(diff) + } + return + case <-ticker.C: + d := time.Since(start) + if diff := d.Seconds() - added; diff > 0 { + t.CounterVec.With(labels).Add(diff) + added += diff + } + } + } + }() + + return func() { + ch <- struct{}{} + } +} + +func (t *TimerContinuousVec) ObserveLabelValues(values ...string) func() { + start := time.Now() + ch := make(chan struct{}) + + go func() { + added := float64(0) + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ch: + d := time.Since(start) + if diff := d.Seconds() - added; diff > 0 { + t.CounterVec.WithLabelValues(values...).Add(diff) + } + return + case <-ticker.C: + d := time.Since(start) + if diff := d.Seconds() - added; diff > 0 { + t.CounterVec.WithLabelValues(values...).Add(diff) + added += diff + } + } + } + }() + + return func() { + ch <- struct{}{} + } +} + +func (t *TimerContinuousVec) Wrap(labels map[string]string, fn func()) { + defer t.Observe(labels)() + fn() +} + +func (t *TimerContinuousVec) WrapLabelValues(values []string, fn func()) { + defer t.ObserveLabelValues(values...)() + fn() +} From eb20df13cc8d4b0a59c1933c9d9ef04769104feb Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Fri, 8 Aug 2025 02:37:44 +0300 Subject: [PATCH 2/4] Lint Signed-off-by: Vladimir Buyanov --- prometheus/timer.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/prometheus/timer.go b/prometheus/timer.go index 330f88487..8d527328e 100644 --- a/prometheus/timer.go +++ b/prometheus/timer.go @@ -13,9 +13,7 @@ package prometheus -import ( - "time" -) +import "time" // Timer is a helper type to time functions. Use NewTimer to create new // instances. @@ -154,7 +152,7 @@ func (t *TimerHistogramVec) Wrap(labels map[string]string, fn func()) { fn() } -func (t *TimerHistogramVec) WrapLV(values []string, fn func()) { +func (t *TimerHistogramVec) WrapLabelValues(values []string, fn func()) { defer t.ObserveLabelValues(values...)() fn() } @@ -201,7 +199,7 @@ func (t *TimerCounter) Observe() func() { return func() { d := time.Since(start) - t.Counter.Add(d.Seconds()) + t.Add(d.Seconds()) } } @@ -227,7 +225,7 @@ func (t *TimerCounterVec) Observe(labels map[string]string) func() { return func() { d := time.Since(start) - t.CounterVec.With(labels).Add(d.Seconds()) + t.With(labels).Add(d.Seconds()) } } @@ -235,7 +233,7 @@ func (t *TimerCounterVec) ObserveLabelValues(values ...string) func() { start := time.Now() return func() { d := time.Since(start) - t.CounterVec.WithLabelValues(values...).Add(d.Seconds()) + t.WithLabelValues(values...).Add(d.Seconds()) } } @@ -292,13 +290,13 @@ func (t *TimerContinuous) Observe() func() { case <-ch: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.Counter.Add(diff) + t.Add(diff) } return case <-ticker.C: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.Counter.Add(diff) + t.Add(diff) added += diff } } @@ -340,13 +338,13 @@ func (t *TimerContinuousVec) Observe(labels map[string]string) func() { case <-ch: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.CounterVec.With(labels).Add(diff) + t.With(labels).Add(diff) } return case <-ticker.C: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.CounterVec.With(labels).Add(diff) + t.With(labels).Add(diff) added += diff } } @@ -371,13 +369,13 @@ func (t *TimerContinuousVec) ObserveLabelValues(values ...string) func() { case <-ch: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.CounterVec.WithLabelValues(values...).Add(diff) + t.WithLabelValues(values...).Add(diff) } return case <-ticker.C: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.CounterVec.WithLabelValues(values...).Add(diff) + t.WithLabelValues(values...).Add(diff) added += diff } } From 7afe36d8237afa9823b519c63aeb1398db0d66a4 Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Thu, 21 Aug 2025 14:29:28 +0300 Subject: [PATCH 3/4] Rethinking timers Signed-off-by: Vladimir Buyanov --- prometheus/timer.go | 358 ++++++++++++++++++-------------------------- 1 file changed, 149 insertions(+), 209 deletions(-) diff --git a/prometheus/timer.go b/prometheus/timer.go index 8d527328e..dfc11f2b8 100644 --- a/prometheus/timer.go +++ b/prometheus/timer.go @@ -13,7 +13,10 @@ package prometheus -import "time" +import ( + "sync" + "time" +) // Timer is a helper type to time functions. Use NewTimer to create new // instances. @@ -80,18 +83,17 @@ func (t *Timer) ObserveDurationWithExemplar(exemplar Labels) time.Duration { return d } -// TimerHistogram is a thin convenience wrapper around a Prometheus Histogram -// that makes it easier to time code paths in an idiomatic Go-style: -// -// defer timer.Observe()() // <-- starts the timer and defers the stop -type TimerHistogram struct { - Histogram +type TimerCounter struct { + Counter + updateInterval time.Duration } -func NewTimerHistogram(opts HistogramOpts) *TimerHistogram { - t := &TimerHistogram{ - Histogram: NewHistogram(opts), +func NewTimerCounter(cnt Counter, updateInterval time.Duration) *TimerCounter { + t := &TimerCounter{ + Counter: cnt, + updateInterval: updateInterval, } + return t } @@ -102,201 +104,33 @@ func NewTimerHistogram(opts HistogramOpts) *TimerHistogram { // // The inner closure calls ObserveDuration on the hidden prom.Timer, recording // the elapsed seconds into the histogram’s current bucket. -func (t *TimerHistogram) Observe() func() { - timer := NewTimer(t.Histogram) - return func() { - timer.ObserveDuration() - } -} - -// Wrap executes fn() and records the time it took. Equivalent to: -// Use when you don't need a defer chain (e.g., inside small helpers). -func (t *TimerHistogram) Wrap(fn func()) { - defer t.Observe()() - fn() -} - -type TimerHistogramVec struct { - *HistogramVec -} - -func NewTimerHistogramVec(opts HistogramOpts, labelNames []string) *TimerHistogramVec { - t := &TimerHistogramVec{ - HistogramVec: NewHistogramVec(opts, labelNames), - } - - return t -} - -// Observe return func for stop timer and observe value -// Example -// defer metric.Observe(map[string]string{"foo": "bar"})() -func (t *TimerHistogramVec) Observe(labels map[string]string) func() { - timeStart := time.Now() - return func() { - d := time.Since(timeStart) - t.HistogramVec.With(labels).Observe(d.Seconds()) - } -} - -func (t *TimerHistogramVec) ObserveLabelValues(values ...string) func() { - timeStart := time.Now() - return func() { - d := time.Since(timeStart) - t.HistogramVec.WithLabelValues(values...).Observe(d.Seconds()) - } -} - -func (t *TimerHistogramVec) Wrap(labels map[string]string, fn func()) { - defer t.Observe(labels)() - fn() -} - -func (t *TimerHistogramVec) WrapLabelValues(values []string, fn func()) { - defer t.ObserveLabelValues(values...)() - fn() -} - -// TimerCounter is a minimal helper that turns a Prometheus **Counter** into a -// “stop-watch” for wall-clock time. -// -// Each call to Observe() starts a timer and, when the returned closure is -// executed, adds the elapsed seconds to the embedded Counter. The counter -// therefore represents **the cumulative running time** across many code paths -// (e.g. total time spent processing all requests since process start). -// -// Compared with a Histogram-based timer you gain: -// -// - A single monotonically-increasing number that is cheap to aggregate or -// alert on (e.g. “CPU-seconds spent in GC”). -// - Zero bucket management or percentile math. -// -// But you lose per-request latency data, so use it when you care about total -// time rather than distribution. -type TimerCounter struct { - Counter -} - -func NewTimerCounter(opts Opts) *TimerCounter { - t := &TimerCounter{ - Counter: NewCounter(CounterOpts(opts)), - } - - return t -} - -// Observe starts a wall-clock timer and returns a “stop” closure. -// -// Typical usage: -// -// defer myCounter.Observe()() // records on function exit -// -// When the closure is executed it records the elapsed duration (in seconds) -// into the Counter. Thread-safe as long as the underlying Counter is -// thread-safe (Prometheus counters are). -func (t *TimerCounter) Observe() func() { - start := time.Now() - - return func() { - d := time.Since(start) - t.Add(d.Seconds()) - } -} - -func (t *TimerCounter) Wrap(fn func()) { - defer t.Observe()() - fn() -} - -type TimerCounterVec struct { - *CounterVec -} - -func NewTimerCounterVec(opts Opts, labels []string) *TimerCounterVec { - t := &TimerCounterVec{ - CounterVec: NewCounterVec(CounterOpts(opts), labels), - } - - return t -} - -func (t *TimerCounterVec) Observe(labels map[string]string) func() { - start := time.Now() - - return func() { - d := time.Since(start) - t.With(labels).Add(d.Seconds()) - } -} - -func (t *TimerCounterVec) ObserveLabelValues(values ...string) func() { - start := time.Now() - return func() { - d := time.Since(start) - t.WithLabelValues(values...).Add(d.Seconds()) - } -} - -func (t *TimerCounterVec) Wrap(labels map[string]string, fn func()) { - defer t.Observe(labels)() - fn() -} - -func (t *TimerCounterVec) WrapLabelValues(values []string, fn func()) { - defer t.ObserveLabelValues(values...)() - fn() -} - -// TimerContinuous is a variant of the standard Timer that **continuously updates** -// its underlying Counter while it is running—by default once every second— -// instead of emitting a single measurement only when the timer stops. -// -// Trade-offs -// ---------- -// - **Higher overhead** than a one-shot Timer (extra goroutine + ticker). -// - **Finer-grained metrics** that are invaluable for long-running or -// indeterminate-length activities such as stream processing, background -// jobs, or large file transfers. -// - **Sensitive to clock skew**—if the system clock is moved **backwards** -// while the timer is running, the negative delta is silently discarded -// (no panic), so that slice of time is lost from the measurement. -type TimerContinuous struct { - Counter - updateInterval time.Duration -} - -func NewTimerContinuous(opts Opts, updateInterval time.Duration) *TimerContinuous { - t := &TimerContinuous{ - Counter: NewCounter(CounterOpts(opts)), - updateInterval: updateInterval, - } - if t.updateInterval == 0 { - t.updateInterval = time.Second - } - - return t -} - -func (t *TimerContinuous) Observe() func() { +func (t *TimerCounter) Observe() (stop func()) { start := time.Now() ch := make(chan struct{}) + wg := &sync.WaitGroup{} + wg.Add(1) go func() { + defer wg.Done() added := float64(0) - ticker := time.NewTicker(t.updateInterval) - defer ticker.Stop() + var updateChan <-chan time.Time + if t.updateInterval > 0 { + ticker := time.NewTicker(t.updateInterval) + defer ticker.Stop() + updateChan = ticker.C + } for { select { case <-ch: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.Add(diff) + t.Counter.Add(diff) } return - case <-ticker.C: + case <-updateChan: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.Add(diff) + t.Counter.Add(diff) added += diff } } @@ -305,46 +139,60 @@ func (t *TimerContinuous) Observe() func() { return func() { ch <- struct{}{} + wg.Wait() } } -func (t *TimerContinuous) Wrap(fn func()) { +func (t *TimerCounter) Wrap(fn func()) { defer t.Observe()() fn() } -type TimerContinuousVec struct { +func (t *TimerCounter) Add(dur time.Duration) { + t.Counter.Add(dur.Seconds()) +} + +type TimerCounterVec struct { *CounterVec + updateInterval time.Duration } -func NewTimerContinuousVec(opts Opts, labels []string) *TimerCounterVec { +func NewTimerCounterVec(cnt *CounterVec, updateInterval time.Duration) *TimerCounterVec { t := &TimerCounterVec{ - CounterVec: NewCounterVec(CounterOpts(opts), labels), + CounterVec: cnt, + updateInterval: updateInterval, } return t } -func (t *TimerContinuousVec) Observe(labels map[string]string) func() { +func (t *TimerCounterVec) Observe(labels map[string]string) (stop func()) { start := time.Now() ch := make(chan struct{}) + wg := &sync.WaitGroup{} + wg.Add(1) go func() { + defer wg.Done() added := float64(0) - ticker := time.NewTicker(time.Second) - defer ticker.Stop() + var updateChan <-chan time.Time + if t.updateInterval > 0 { + ticker := time.NewTicker(t.updateInterval) + defer ticker.Stop() + updateChan = ticker.C + } for { select { case <-ch: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.With(labels).Add(diff) + t.CounterVec.With(labels).Add(diff) } return - case <-ticker.C: + case <-updateChan: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.With(labels).Add(diff) + t.CounterVec.With(labels).Add(diff) added += diff } } @@ -353,29 +201,37 @@ func (t *TimerContinuousVec) Observe(labels map[string]string) func() { return func() { ch <- struct{}{} + wg.Wait() } } -func (t *TimerContinuousVec) ObserveLabelValues(values ...string) func() { +func (t *TimerCounterVec) ObserveLabelValues(values ...string) (stop func()) { start := time.Now() ch := make(chan struct{}) + wg := &sync.WaitGroup{} + wg.Add(1) go func() { + defer wg.Done() added := float64(0) - ticker := time.NewTicker(time.Second) - defer ticker.Stop() + var updateChan <-chan time.Time + if t.updateInterval > 0 { + ticker := time.NewTicker(t.updateInterval) + defer ticker.Stop() + updateChan = ticker.C + } for { select { case <-ch: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.WithLabelValues(values...).Add(diff) + t.CounterVec.WithLabelValues(values...).Add(diff) } return - case <-ticker.C: + case <-updateChan: d := time.Since(start) if diff := d.Seconds() - added; diff > 0 { - t.WithLabelValues(values...).Add(diff) + t.CounterVec.WithLabelValues(values...).Add(diff) added += diff } } @@ -384,15 +240,99 @@ func (t *TimerContinuousVec) ObserveLabelValues(values ...string) func() { return func() { ch <- struct{}{} + wg.Wait() + } +} + +func (t *TimerCounterVec) Wrap(labels map[string]string, fn func()) { + defer t.Observe(labels)() + fn() +} + +func (t *TimerCounterVec) WrapLabelValues(values []string, fn func()) { + defer t.ObserveLabelValues(values...)() + fn() +} + +func (t *TimerCounterVec) Add(dur time.Duration, labels map[string]string) { + t.CounterVec.With(labels).Add(dur.Seconds()) +} + +func (t *TimerCounterVec) AddLabelValues(dur time.Duration, values ...string) { + t.CounterVec.WithLabelValues(values...).Add(dur.Seconds()) +} + +type TimerObserver struct { + Observer +} + +func NewTimerObserver(obs Observer) *TimerObserver { + t := &TimerObserver{ + Observer: obs, + } + + return t +} + +func (t *TimerObserver) Observe() (stop func()) { + start := time.Now() + return func() { + d := time.Since(start) + t.Observer.Observe(d.Seconds()) + } +} + +func (t *TimerObserver) Wrap(fn func()) { + defer t.Observe()() + fn() +} + +func (t *TimerObserver) Add(dur time.Duration) { + t.Observer.Observe(dur.Seconds()) +} + +type TimerObserverVec struct { + ObserverVec +} + +func NewTimerObserverVec(obs ObserverVec) *TimerObserverVec { + t := &TimerObserverVec{ + ObserverVec: obs, + } + + return t +} + +func (t *TimerObserverVec) Observe(labels map[string]string) func() { + start := time.Now() + return func() { + d := time.Since(start) + t.ObserverVec.With(labels).Observe(d.Seconds()) } } -func (t *TimerContinuousVec) Wrap(labels map[string]string, fn func()) { +func (t *TimerObserverVec) ObserveLabelValues(values ...string) func() { + start := time.Now() + return func() { + d := time.Since(start) + t.ObserverVec.WithLabelValues(values...).Observe(d.Seconds()) + } +} + +func (t *TimerObserverVec) Wrap(labels map[string]string, fn func()) { defer t.Observe(labels)() fn() } -func (t *TimerContinuousVec) WrapLabelValues(values []string, fn func()) { +func (t *TimerObserverVec) WrapLabelValues(values []string, fn func()) { defer t.ObserveLabelValues(values...)() fn() } + +func (t *TimerObserverVec) Add(dur time.Duration, labels map[string]string) { + t.ObserverVec.With(labels).Observe(dur.Seconds()) +} + +func (t *TimerObserverVec) AddLabelValues(dur time.Duration, values ...string) { + t.ObserverVec.WithLabelValues(values...).Observe(dur.Seconds()) +} From 58f68aef54b218e042445c116be2153e7c787390 Mon Sep 17 00:00:00 2001 From: Vladimir Buyanov Date: Fri, 22 Aug 2025 20:15:56 +0300 Subject: [PATCH 4/4] Fix Observer interface --- prometheus/observer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/prometheus/observer.go b/prometheus/observer.go index 03773b21f..b666a90ae 100644 --- a/prometheus/observer.go +++ b/prometheus/observer.go @@ -17,6 +17,7 @@ package prometheus // Histogram and Summary to add observations. type Observer interface { Observe(float64) + Collector } // The ObserverFunc type is an adapter to allow the use of ordinary