@@ -2,6 +2,7 @@ package dbrollup
22
33import (
44 "context"
5+ "flag"
56 "time"
67
78 "golang.org/x/sync/errgroup"
@@ -19,36 +20,68 @@ const (
1920 DefaultInterval = 5 * time .Minute
2021)
2122
23+ type Event struct {
24+ TemplateUsageStats bool
25+ }
26+
2227type Rolluper struct {
23- cancel context.CancelFunc
24- closed chan struct {}
25- db database.Store
26- logger slog.Logger
28+ cancel context.CancelFunc
29+ closed chan struct {}
30+ db database.Store
31+ logger slog.Logger
32+ interval time.Duration
33+ event chan <- Event
34+ }
35+
36+ type Option func (* Rolluper )
37+
38+ // WithInterval sets the interval between rollups.
39+ func WithInterval (interval time.Duration ) Option {
40+ return func (r * Rolluper ) {
41+ r .interval = interval
42+ }
43+ }
44+
45+ // WithEventChannel sets the event channel to use for rollup events.
46+ //
47+ // This is only used for testing.
48+ func WithEventChannel (ch chan <- Event ) Option {
49+ if flag .Lookup ("test.v" ) == nil {
50+ panic ("developer error: WithEventChannel is not to be used outside of tests" )
51+ }
52+ return func (r * Rolluper ) {
53+ r .event = ch
54+ }
2755}
2856
2957// New creates a new DB rollup service that periodically runs rollup queries.
3058// It is the caller's responsibility to call Close on the returned instance.
3159//
3260// This is for e.g. generating insights data (template_usage_stats) from
3361// raw data (workspace_agent_stats, workspace_app_stats).
34- func New (logger slog.Logger , db database.Store , interval time. Duration ) * Rolluper {
62+ func New (logger slog.Logger , db database.Store , opts ... Option ) * Rolluper {
3563 ctx , cancel := context .WithCancel (context .Background ())
3664
3765 r := & Rolluper {
38- cancel : cancel ,
39- closed : make (chan struct {}),
40- db : db ,
41- logger : logger .Named ("dbrollup" ),
66+ cancel : cancel ,
67+ closed : make (chan struct {}),
68+ db : db ,
69+ logger : logger ,
70+ interval : DefaultInterval ,
71+ }
72+
73+ for _ , opt := range opts {
74+ opt (r )
4275 }
4376
4477 //nolint:gocritic // The system rolls up database tables without user input.
4578 ctx = dbauthz .AsSystemRestricted (ctx )
46- go r .start (ctx , interval )
79+ go r .start (ctx )
4780
4881 return r
4982}
5083
51- func (r * Rolluper ) start (ctx context.Context , interval time. Duration ) {
84+ func (r * Rolluper ) start (ctx context.Context ) {
5285 defer close (r .closed )
5386
5487 do := func () {
@@ -58,7 +91,7 @@ func (r *Rolluper) start(ctx context.Context, interval time.Duration) {
5891 now := time .Now ()
5992
6093 // Track whether or not we performed a rollup (we got the advisory lock).
61- templateUsageStats := false
94+ var ev Event
6295
6396 eg .Go (func () error {
6497 return r .db .InTx (func (tx database.Store ) error {
@@ -72,7 +105,7 @@ func (r *Rolluper) start(ctx context.Context, interval time.Duration) {
72105 return nil
73106 }
74107
75- templateUsageStats = true
108+ ev . TemplateUsageStats = true
76109 return tx .UpsertTemplateUsageStats (ctx )
77110 }, nil )
78111 })
@@ -86,12 +119,22 @@ func (r *Rolluper) start(ctx context.Context, interval time.Duration) {
86119 if ctx .Err () == nil {
87120 r .logger .Error (ctx , "failed to rollup data" , slog .Error (err ))
88121 }
89- } else {
90- r .logger .Debug (ctx ,
91- "rolled up data" ,
92- slog .F ("took" , time .Since (now )),
93- slog .F ("template_usage_stats" , templateUsageStats ),
94- )
122+ return
123+ }
124+
125+ r .logger .Debug (ctx ,
126+ "rolled up data" ,
127+ slog .F ("took" , time .Since (now )),
128+ slog .F ("event" , ev ),
129+ )
130+
131+ // For testing.
132+ if r .event != nil {
133+ select {
134+ case <- ctx .Done ():
135+ return
136+ case r .event <- ev :
137+ }
95138 }
96139 }
97140
@@ -108,11 +151,11 @@ func (r *Rolluper) start(ctx context.Context, interval time.Duration) {
108151 case <- t .C :
109152 // Ensure we're on the interval.
110153 now := time .Now ()
111- next := now .Add (interval ).Truncate (interval ) // Ensure we're on the interval and synced with the clock.
154+ next := now .Add (r . interval ).Truncate (r . interval ) // Ensure we're on the interval and synced with the clock.
112155 d := next .Sub (now )
113156 // Safety check (shouldn't be possible).
114157 if d <= 0 {
115- d = interval
158+ d = r . interval
116159 }
117160 t .Reset (d )
118161
0 commit comments