@@ -14,9 +14,11 @@ import (
14
14
"github.com/ThreeDotsLabs/watermill/message"
15
15
"github.com/google/uuid"
16
16
"github.com/rs/zerolog"
17
+ "go.opentelemetry.io/otel"
17
18
18
19
"github.com/mindersec/minder/internal/db"
19
20
remindermessages "github.com/mindersec/minder/internal/reminder/messages"
21
+ "github.com/mindersec/minder/internal/reminder/metrics"
20
22
reminderconfig "github.com/mindersec/minder/pkg/config/reminder"
21
23
"github.com/mindersec/minder/pkg/eventer/constants"
22
24
)
@@ -42,6 +44,8 @@ type reminder struct {
42
44
ticker * time.Ticker
43
45
44
46
eventPublisher message.Publisher
47
+
48
+ metrics * metrics.Metrics
45
49
}
46
50
47
51
// NewReminder creates a new reminder instance
@@ -74,21 +78,52 @@ func (r *reminder) Start(ctx context.Context) error {
74
78
return errors .New ("reminder stopped, cannot start again" )
75
79
default :
76
80
}
81
+ defer r .Stop ()
77
82
78
83
interval := r .cfg .RecurrenceConfig .Interval
79
84
if interval <= 0 {
80
85
return fmt .Errorf ("invalid interval: %s" , r .cfg .RecurrenceConfig .Interval )
81
86
}
82
87
88
+ metricsServerDone := make (chan struct {})
89
+
90
+ if r .cfg .MetricsConfig .Enabled {
91
+ metricsProviderReady := make (chan struct {})
92
+
93
+ go func () {
94
+ if err := r .startMetricServer (ctx , metricsProviderReady ); err != nil {
95
+ logger .Fatal ().Err (err ).Msg ("failed to start metrics server" )
96
+ }
97
+ close (metricsServerDone )
98
+ }()
99
+
100
+ select {
101
+ case <- metricsProviderReady :
102
+ var err error
103
+ r .metrics , err = metrics .NewMetrics (otel .Meter ("reminder" ))
104
+ if err != nil {
105
+ return err
106
+ }
107
+ case <- ctx .Done ():
108
+ logger .Info ().Msg ("reminder stopped" )
109
+ return nil
110
+ }
111
+ }
112
+
83
113
r .ticker = time .NewTicker (interval )
84
- defer r .Stop ()
85
114
86
115
for {
87
116
select {
88
117
case <- ctx .Done ():
118
+ if r .cfg .MetricsConfig .Enabled {
119
+ <- metricsServerDone
120
+ }
89
121
logger .Info ().Msg ("reminder stopped" )
90
122
return nil
91
123
case <- r .stop :
124
+ if r .cfg .MetricsConfig .Enabled {
125
+ <- metricsServerDone
126
+ }
92
127
logger .Info ().Msg ("reminder stopped" )
93
128
return nil
94
129
case <- r .ticker .C :
@@ -126,7 +161,7 @@ func (r *reminder) sendReminders(ctx context.Context) error {
126
161
logger := zerolog .Ctx (ctx )
127
162
128
163
// Fetch a batch of repositories
129
- repos , err := r .getRepositoryBatch (ctx )
164
+ repos , repoToLastUpdated , err := r .getRepositoryBatch (ctx )
130
165
if err != nil {
131
166
return fmt .Errorf ("error fetching repository batch: %w" , err )
132
167
}
@@ -143,6 +178,10 @@ func (r *reminder) sendReminders(ctx context.Context) error {
143
178
return fmt .Errorf ("error creating reminder messages: %w" , err )
144
179
}
145
180
181
+ if r .metrics != nil {
182
+ r .metrics .RecordBatch (ctx , int64 (len (repos )))
183
+ }
184
+
146
185
err = r .eventPublisher .Publish (constants .TopicQueueRepoReminder , messages ... )
147
186
if err != nil {
148
187
return fmt .Errorf ("error publishing messages: %w" , err )
@@ -151,13 +190,16 @@ func (r *reminder) sendReminders(ctx context.Context) error {
151
190
repoIds := make ([]uuid.UUID , len (repos ))
152
191
for _ , repo := range repos {
153
192
repoIds = append (repoIds , repo .ID )
154
- }
193
+ if r .metrics != nil {
194
+ sendDelay := time .Since (repoToLastUpdated [repo .ID ]) - r .cfg .RecurrenceConfig .MinElapsed
155
195
156
- // TODO: Collect Metrics
157
- // Potential metrics:
158
- // - Gauge: Number of reminders in the current batch
159
- // - UpDownCounter: Average reminders sent per batch
160
- // - Histogram: reminder_last_sent time distribution
196
+ recorder := r .metrics .SendDelay
197
+ if ! repo .ReminderLastSent .Valid {
198
+ recorder = r .metrics .NewSendDelay
199
+ }
200
+ recorder .Record (ctx , sendDelay .Seconds ())
201
+ }
202
+ }
161
203
162
204
err = r .store .UpdateReminderLastSentForRepositories (ctx , repoIds )
163
205
if err != nil {
@@ -167,7 +209,7 @@ func (r *reminder) sendReminders(ctx context.Context) error {
167
209
return nil
168
210
}
169
211
170
- func (r * reminder ) getRepositoryBatch (ctx context.Context ) ([]db.Repository , error ) {
212
+ func (r * reminder ) getRepositoryBatch (ctx context.Context ) ([]db.Repository , map [uuid. UUID ]time. Time , error ) {
171
213
logger := zerolog .Ctx (ctx )
172
214
173
215
logger .Debug ().Msgf ("fetching repositories after cursor: %s" , r .repositoryCursor )
@@ -176,21 +218,23 @@ func (r *reminder) getRepositoryBatch(ctx context.Context) ([]db.Repository, err
176
218
Limit : int64 (r .cfg .RecurrenceConfig .BatchSize ),
177
219
})
178
220
if err != nil {
179
- return nil , err
221
+ return nil , nil , err
180
222
}
181
223
182
- eligibleRepos , err := r .getEligibleRepositories (ctx , repos )
224
+ eligibleRepos , eligibleReposLastUpdated , err := r .getEligibleRepositories (ctx , repos )
183
225
if err != nil {
184
- return nil , err
226
+ return nil , nil , err
185
227
}
186
228
logger .Debug ().Msgf ("%d/%d repositories are eligible for reminders" , len (eligibleRepos ), len (repos ))
187
229
188
230
r .updateRepositoryCursor (ctx , repos )
189
231
190
- return eligibleRepos , nil
232
+ return eligibleRepos , eligibleReposLastUpdated , nil
191
233
}
192
234
193
- func (r * reminder ) getEligibleRepositories (ctx context.Context , repos []db.Repository ) ([]db.Repository , error ) {
235
+ func (r * reminder ) getEligibleRepositories (ctx context.Context , repos []db.Repository ) (
236
+ []db.Repository , map [uuid.UUID ]time.Time , error ,
237
+ ) {
194
238
eligibleRepos := make ([]db.Repository , 0 , len (repos ))
195
239
196
240
// We have a slice of repositories, but the sqlc-generated code wants a slice of UUIDs,
@@ -202,11 +246,11 @@ func (r *reminder) getEligibleRepositories(ctx context.Context, repos []db.Repos
202
246
}
203
247
oldestRuleEvals , err := r .store .ListOldestRuleEvaluationsByRepositoryId (ctx , repoIds )
204
248
if err != nil {
205
- return nil , err
249
+ return nil , nil , err
206
250
}
207
251
idToLastUpdate := make (map [uuid.UUID ]time.Time , len (oldestRuleEvals ))
208
- for _ , times := range oldestRuleEvals {
209
- idToLastUpdate [times .RepositoryID ] = times .OldestLastUpdated
252
+ for _ , ruleEval := range oldestRuleEvals {
253
+ idToLastUpdate [ruleEval .RepositoryID ] = ruleEval .OldestLastUpdated
210
254
}
211
255
212
256
cutoff := time .Now ().Add (- 1 * r .cfg .RecurrenceConfig .MinElapsed )
@@ -216,7 +260,7 @@ func (r *reminder) getEligibleRepositories(ctx context.Context, repos []db.Repos
216
260
}
217
261
}
218
262
219
- return eligibleRepos , nil
263
+ return eligibleRepos , idToLastUpdate , nil
220
264
}
221
265
222
266
func (r * reminder ) updateRepositoryCursor (ctx context.Context , repos []db.Repository ) {
0 commit comments