Skip to content

Commit 69f0d6c

Browse files
committed
Timer helpers
1 parent 14ccb93 commit 69f0d6c

File tree

1 file changed

+320
-1
lines changed

1 file changed

+320
-1
lines changed

prometheus/timer.go

Lines changed: 320 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
package prometheus
1515

16-
import "time"
16+
import (
17+
"time"
18+
)
1719

1820
// Timer is a helper type to time functions. Use NewTimer to create new
1921
// instances.
@@ -79,3 +81,320 @@ func (t *Timer) ObserveDurationWithExemplar(exemplar Labels) time.Duration {
7981
}
8082
return d
8183
}
84+
85+
// TimerHistogram is a thin convenience wrapper around a Prometheus Histogram
86+
// that makes it easier to time code paths in an idiomatic Go-style:
87+
//
88+
// defer timer.Observe()() // <-- starts the timer and defers the stop
89+
type TimerHistogram struct {
90+
Histogram
91+
}
92+
93+
func NewTimerHistogram(opts HistogramOpts) *TimerHistogram {
94+
t := &TimerHistogram{
95+
Histogram: NewHistogram(opts),
96+
}
97+
return t
98+
}
99+
100+
// Observe starts a prom.Timer that records into the embedded Histogram and
101+
// returns a “stop” callback. Best used with defer:
102+
//
103+
// defer timer.Observe()()
104+
//
105+
// The inner closure calls ObserveDuration on the hidden prom.Timer, recording
106+
// the elapsed seconds into the histogram’s current bucket.
107+
func (t *TimerHistogram) Observe() func() {
108+
timer := NewTimer(t.Histogram)
109+
return func() {
110+
timer.ObserveDuration()
111+
}
112+
}
113+
114+
// Wrap executes fn() and records the time it took. Equivalent to:
115+
// Use when you don't need a defer chain (e.g., inside small helpers).
116+
func (t *TimerHistogram) Wrap(fn func()) {
117+
defer t.Observe()()
118+
fn()
119+
}
120+
121+
type TimerHistogramVec struct {
122+
*HistogramVec
123+
}
124+
125+
func NewTimerHistogramVec(opts HistogramOpts, labelNames []string) *TimerHistogramVec {
126+
t := &TimerHistogramVec{
127+
HistogramVec: NewHistogramVec(opts, labelNames),
128+
}
129+
130+
return t
131+
}
132+
133+
// Observe return func for stop timer and observe value
134+
// Example
135+
// defer metric.Observe(map[string]string{"foo": "bar"})()
136+
func (t *TimerHistogramVec) Observe(labels map[string]string) func() {
137+
timeStart := time.Now()
138+
return func() {
139+
d := time.Since(timeStart)
140+
t.HistogramVec.With(labels).Observe(d.Seconds())
141+
}
142+
}
143+
144+
func (t *TimerHistogramVec) ObserveLabelValues(values ...string) func() {
145+
timeStart := time.Now()
146+
return func() {
147+
d := time.Since(timeStart)
148+
t.HistogramVec.WithLabelValues(values...).Observe(d.Seconds())
149+
}
150+
}
151+
152+
func (t *TimerHistogramVec) Wrap(labels map[string]string, fn func()) {
153+
defer t.Observe(labels)()
154+
fn()
155+
}
156+
157+
func (t *TimerHistogramVec) WrapLV(values []string, fn func()) {
158+
defer t.ObserveLabelValues(values...)()
159+
fn()
160+
}
161+
162+
// TimerCounter is a minimal helper that turns a Prometheus **Counter** into a
163+
// “stop-watch” for wall-clock time.
164+
//
165+
// Each call to Observe() starts a timer and, when the returned closure is
166+
// executed, adds the elapsed seconds to the embedded Counter. The counter
167+
// therefore represents **the cumulative running time** across many code paths
168+
// (e.g. total time spent processing all requests since process start).
169+
//
170+
// Compared with a Histogram-based timer you gain:
171+
//
172+
// - A single monotonically-increasing number that is cheap to aggregate or
173+
// alert on (e.g. “CPU-seconds spent in GC”).
174+
// - Zero bucket management or percentile math.
175+
//
176+
// But you lose per-request latency data, so use it when you care about total
177+
// time rather than distribution.
178+
type TimerCounter struct {
179+
Counter
180+
}
181+
182+
func NewTimerCounter(opts Opts) *TimerCounter {
183+
t := &TimerCounter{
184+
Counter: NewCounter(CounterOpts(opts)),
185+
}
186+
187+
return t
188+
}
189+
190+
// Observe starts a wall-clock timer and returns a “stop” closure.
191+
//
192+
// Typical usage:
193+
//
194+
// defer myCounter.Observe()() // records on function exit
195+
//
196+
// When the closure is executed it records the elapsed duration (in seconds)
197+
// into the Counter. Thread-safe as long as the underlying Counter is
198+
// thread-safe (Prometheus counters are).
199+
func (t *TimerCounter) Observe() func() {
200+
start := time.Now()
201+
202+
return func() {
203+
d := time.Since(start)
204+
t.Counter.Add(d.Seconds())
205+
}
206+
}
207+
208+
func (t *TimerCounter) Wrap(fn func()) {
209+
defer t.Observe()()
210+
fn()
211+
}
212+
213+
type TimerCounterVec struct {
214+
*CounterVec
215+
}
216+
217+
func NewTimerCounterVec(opts Opts, labels []string) *TimerCounterVec {
218+
t := &TimerCounterVec{
219+
CounterVec: NewCounterVec(CounterOpts(opts), labels),
220+
}
221+
222+
return t
223+
}
224+
225+
func (t *TimerCounterVec) Observe(labels map[string]string) func() {
226+
start := time.Now()
227+
228+
return func() {
229+
d := time.Since(start)
230+
t.CounterVec.With(labels).Add(d.Seconds())
231+
}
232+
}
233+
234+
func (t *TimerCounterVec) ObserveLabelValues(values ...string) func() {
235+
start := time.Now()
236+
return func() {
237+
d := time.Since(start)
238+
t.CounterVec.WithLabelValues(values...).Add(d.Seconds())
239+
}
240+
}
241+
242+
func (t *TimerCounterVec) Wrap(labels map[string]string, fn func()) {
243+
defer t.Observe(labels)()
244+
fn()
245+
}
246+
247+
func (t *TimerCounterVec) WrapLabelValues(values []string, fn func()) {
248+
defer t.ObserveLabelValues(values...)()
249+
fn()
250+
}
251+
252+
// TimerContinuous is a variant of the standard Timer that **continuously updates**
253+
// its underlying Counter while it is running—by default once every second—
254+
// instead of emitting a single measurement only when the timer stops.
255+
//
256+
// Trade-offs
257+
// ----------
258+
// - **Higher overhead** than a one-shot Timer (extra goroutine + ticker).
259+
// - **Finer-grained metrics** that are invaluable for long-running or
260+
// indeterminate-length activities such as stream processing, background
261+
// jobs, or large file transfers.
262+
// - **Sensitive to clock skew**—if the system clock is moved **backwards**
263+
// while the timer is running, the negative delta is silently discarded
264+
// (no panic), so that slice of time is lost from the measurement.
265+
type TimerContinuous struct {
266+
Counter
267+
updateInterval time.Duration
268+
}
269+
270+
func NewTimerContinuous(opts Opts, updateInterval time.Duration) *TimerContinuous {
271+
t := &TimerContinuous{
272+
Counter: NewCounter(CounterOpts(opts)),
273+
updateInterval: updateInterval,
274+
}
275+
if t.updateInterval == 0 {
276+
t.updateInterval = time.Second
277+
}
278+
279+
return t
280+
}
281+
282+
func (t *TimerContinuous) Observe() func() {
283+
start := time.Now()
284+
ch := make(chan struct{})
285+
286+
go func() {
287+
added := float64(0)
288+
ticker := time.NewTicker(t.updateInterval)
289+
defer ticker.Stop()
290+
for {
291+
select {
292+
case <-ch:
293+
d := time.Since(start)
294+
if diff := d.Seconds() - added; diff > 0 {
295+
t.Counter.Add(diff)
296+
}
297+
return
298+
case <-ticker.C:
299+
d := time.Since(start)
300+
if diff := d.Seconds() - added; diff > 0 {
301+
t.Counter.Add(diff)
302+
added += diff
303+
}
304+
}
305+
}
306+
}()
307+
308+
return func() {
309+
ch <- struct{}{}
310+
}
311+
}
312+
313+
func (t *TimerContinuous) Wrap(fn func()) {
314+
defer t.Observe()()
315+
fn()
316+
}
317+
318+
type TimerContinuousVec struct {
319+
*CounterVec
320+
}
321+
322+
func NewTimerContinuousVec(opts Opts, labels []string) *TimerCounterVec {
323+
t := &TimerCounterVec{
324+
CounterVec: NewCounterVec(CounterOpts(opts), labels),
325+
}
326+
327+
return t
328+
}
329+
330+
func (t *TimerContinuousVec) Observe(labels map[string]string) func() {
331+
start := time.Now()
332+
ch := make(chan struct{})
333+
334+
go func() {
335+
added := float64(0)
336+
ticker := time.NewTicker(time.Second)
337+
defer ticker.Stop()
338+
for {
339+
select {
340+
case <-ch:
341+
d := time.Since(start)
342+
if diff := d.Seconds() - added; diff > 0 {
343+
t.CounterVec.With(labels).Add(diff)
344+
}
345+
return
346+
case <-ticker.C:
347+
d := time.Since(start)
348+
if diff := d.Seconds() - added; diff > 0 {
349+
t.CounterVec.With(labels).Add(diff)
350+
added += diff
351+
}
352+
}
353+
}
354+
}()
355+
356+
return func() {
357+
ch <- struct{}{}
358+
}
359+
}
360+
361+
func (t *TimerContinuousVec) ObserveLabelValues(values ...string) func() {
362+
start := time.Now()
363+
ch := make(chan struct{})
364+
365+
go func() {
366+
added := float64(0)
367+
ticker := time.NewTicker(time.Second)
368+
defer ticker.Stop()
369+
for {
370+
select {
371+
case <-ch:
372+
d := time.Since(start)
373+
if diff := d.Seconds() - added; diff > 0 {
374+
t.CounterVec.WithLabelValues(values...).Add(diff)
375+
}
376+
return
377+
case <-ticker.C:
378+
d := time.Since(start)
379+
if diff := d.Seconds() - added; diff > 0 {
380+
t.CounterVec.WithLabelValues(values...).Add(diff)
381+
added += diff
382+
}
383+
}
384+
}
385+
}()
386+
387+
return func() {
388+
ch <- struct{}{}
389+
}
390+
}
391+
392+
func (t *TimerContinuousVec) Wrap(labels map[string]string, fn func()) {
393+
defer t.Observe(labels)()
394+
fn()
395+
}
396+
397+
func (t *TimerContinuousVec) WrapLabelValues(values []string, fn func()) {
398+
defer t.ObserveLabelValues(values...)()
399+
fn()
400+
}

0 commit comments

Comments
 (0)