2016-06-06 04:56:58 -05:00
package alerting
import (
2022-04-22 14:09:47 -05:00
"fmt"
2016-06-06 04:56:58 -05:00
"time"
"github.com/benbjohnson/clock"
2022-04-22 14:09:47 -05:00
2022-06-01 10:48:10 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2022-04-22 14:09:47 -05:00
"github.com/grafana/grafana/pkg/services/alerting/metrics"
2016-06-06 04:56:58 -05:00
)
2019-05-20 05:13:32 -05:00
// Ticker is a ticker to power the alerting scheduler. it's like a time.Ticker, except:
2016-06-06 04:56:58 -05:00
// * it doesn't drop ticks for slow receivers, rather, it queues up. so that callers are in control to instrument what's going on.
2022-04-22 14:09:47 -05:00
// * it ticks on interval marks or very shortly after. this provides a predictable load pattern
2016-06-06 04:56:58 -05:00
// (this shouldn't cause too much load contention issues because the next steps in the pipeline just process at their own pace)
// * the timestamps are used to mark "last datapoint to query for" and as such, are a configurable amount of seconds in the past
type Ticker struct {
2022-04-22 14:09:47 -05:00
C chan time . Time
clock clock . Clock
last time . Time
interval time . Duration
metrics * metrics . Ticker
2022-06-01 10:48:10 -05:00
stopCh chan struct { }
2016-06-06 04:56:58 -05:00
}
2022-04-22 14:09:47 -05:00
// NewTicker returns a Ticker that ticks on interval marks (or very shortly after) starting at c.Now(), and never drops ticks. interval should not be negative or zero.
func NewTicker ( c clock . Clock , interval time . Duration , metric * metrics . Ticker ) * Ticker {
if interval <= 0 {
panic ( fmt . Errorf ( "non-positive interval [%v] is not allowed" , interval ) )
}
2016-06-06 04:56:58 -05:00
t := & Ticker {
2022-04-22 14:09:47 -05:00
C : make ( chan time . Time ) ,
clock : c ,
2022-06-10 09:27:17 -05:00
last : getStartTick ( c , interval ) ,
2022-04-22 14:09:47 -05:00
interval : interval ,
metrics : metric ,
2022-06-01 10:48:10 -05:00
stopCh : make ( chan struct { } ) ,
2016-06-06 04:56:58 -05:00
}
2022-04-22 14:09:47 -05:00
metric . IntervalSeconds . Set ( t . interval . Seconds ( ) ) // Seconds report fractional part as well, so it matches the format of the timestamp we report below
2016-06-06 04:56:58 -05:00
go t . run ( )
return t
}
2022-06-10 09:27:17 -05:00
func getStartTick ( clk clock . Clock , interval time . Duration ) time . Time {
nano := clk . Now ( ) . UnixNano ( )
return time . Unix ( 0 , nano - ( nano % interval . Nanoseconds ( ) ) )
}
2016-06-06 04:56:58 -05:00
func ( t * Ticker ) run ( ) {
2022-06-01 10:48:10 -05:00
logger := log . New ( "ticker" )
2022-06-10 09:27:17 -05:00
logger . Info ( "starting" , "first_tick" , t . last . Add ( t . interval ) )
2022-06-01 10:48:10 -05:00
LOOP :
2016-06-06 04:56:58 -05:00
for {
2022-04-22 14:09:47 -05:00
next := t . last . Add ( t . interval ) // calculate the time of the next tick
t . metrics . NextTickTime . Set ( float64 ( next . UnixNano ( ) ) / 1e9 )
diff := t . clock . Now ( ) . Sub ( next ) // calculate the difference between the current time and the next tick
// if difference is not negative, then it should tick
2016-06-06 04:56:58 -05:00
if diff >= 0 {
2022-06-01 10:48:10 -05:00
select {
case t . C <- next :
case <- t . stopCh :
break LOOP
}
2016-06-06 04:56:58 -05:00
t . last = next
2022-04-22 14:09:47 -05:00
t . metrics . LastTickTime . Set ( float64 ( next . UnixNano ( ) ) / 1e9 )
2016-06-06 04:56:58 -05:00
continue
}
// tick is too young. try again when ...
2022-06-01 10:48:10 -05:00
select {
case <- t . clock . After ( - diff ) : // ...it'll definitely be old enough
case <- t . stopCh :
break LOOP
}
}
logger . Info ( "stopped" , "last_tick" , t . last )
}
// Stop stops the ticker. It does not close the C channel
func ( t * Ticker ) Stop ( ) {
select {
case t . stopCh <- struct { } { } :
default :
// already stopped
2016-06-06 04:56:58 -05:00
}
}