@@ -8,12 +8,12 @@ import (
8
8
"net/http"
9
9
"time"
10
10
11
+ "github.com/google/uuid"
11
12
"golang.org/x/xerrors"
12
13
13
14
"cdr.dev/slog"
14
15
"cdr.dev/slog/sloggers/sloghuman"
15
16
16
- "github.com/coder/coder/v2/coderd/notifications"
17
17
"github.com/coder/coder/v2/coderd/tracing"
18
18
"github.com/coder/coder/v2/codersdk"
19
19
"github.com/coder/coder/v2/scaletest/createusers"
@@ -28,14 +28,15 @@ type Runner struct {
28
28
29
29
createUserRunner * createusers.Runner
30
30
31
- userCreatedNotificationLatency time. Duration
32
- userDeletedNotificationLatency time.Duration
31
+ // notificationLatencies stores the latency for each notification type
32
+ notificationLatencies map [uuid. UUID ] time.Duration
33
33
}
34
34
35
35
func NewRunner (client * codersdk.Client ,cfg Config )* Runner {
36
36
return & Runner {
37
- client :client ,
38
- cfg :cfg ,
37
+ client :client ,
38
+ cfg :cfg ,
39
+ notificationLatencies :make (map [uuid.UUID ]time.Duration ),
39
40
}
40
41
}
41
42
@@ -73,7 +74,7 @@ func (r *Runner) Run(ctx context.Context, id string, logs io.Writer) error {
73
74
codersdk .WithLogger (logger ),
74
75
codersdk .WithLogBodies ())
75
76
76
- logger .Info (ctx ,"user created" ,slog .F ("username" ,newUser .Username ),slog .F ("user_id" ,newUser .ID .String ()))
77
+ logger .Info (ctx ,"runner user created" ,slog .F ("username" ,newUser .Username ),slog .F ("user_id" ,newUser .ID .String ()))
77
78
78
79
if r .cfg .IsOwner {
79
80
logger .Info (ctx ,"assigning Owner role to user" )
@@ -128,7 +129,7 @@ func (r *Runner) Run(ctx context.Context, id string, logs io.Writer) error {
128
129
watchCtx ,cancel := context .WithTimeout (ctx ,r .cfg .NotificationTimeout )
129
130
defer cancel ()
130
131
131
- if err := r .watchNotifications (watchCtx ,conn ,newUser ,logger );err != nil {
132
+ if err := r .watchNotifications (watchCtx ,conn ,newUser ,logger , r . cfg . ExpectedNotifications );err != nil {
132
133
return xerrors .Errorf ("notification watch failed: %w" ,err )
133
134
}
134
135
@@ -146,20 +147,16 @@ func (r *Runner) Cleanup(ctx context.Context, id string, logs io.Writer) error {
146
147
return nil
147
148
}
148
149
149
- const (
150
- UserCreatedNotificationLatencyMetric = "user_created_notification_latency_seconds"
151
- UserDeletedNotificationLatencyMetric = "user_deleted_notification_latency_seconds"
152
- )
150
+ const NotificationDeliveryLatencyMetric = "notification_delivery_latency_seconds"
153
151
154
152
func (r * Runner )GetMetrics ()map [string ]any {
155
153
metrics := map [string ]any {}
156
154
157
- if r .userCreatedNotificationLatency > 0 {
158
- metrics [UserCreatedNotificationLatencyMetric ]= r .userCreatedNotificationLatency .Seconds ()
159
- }
160
-
161
- if r .userDeletedNotificationLatency > 0 {
162
- metrics [UserDeletedNotificationLatencyMetric ]= r .userDeletedNotificationLatency .Seconds ()
155
+ for templateID ,latency := range r .notificationLatencies {
156
+ if latency > 0 {
157
+ metricKey := fmt .Sprintf ("%s_%s" ,NotificationDeliveryLatencyMetric ,templateID .String ())
158
+ metrics [metricKey ]= latency .Seconds ()
159
+ }
163
160
}
164
161
165
162
return metrics
@@ -193,47 +190,68 @@ func (r *Runner) dialNotificationWebsocket(ctx context.Context, client *codersdk
193
190
return conn ,nil
194
191
}
195
192
196
- // watchNotifications reads notifications from the websockert and returns error or nil
197
- // once both expected notifications are received.
198
- func (r * Runner )watchNotifications (ctx context.Context ,conn * websocket.Conn ,user codersdk.User ,logger slog.Logger )error {
199
- notificationStartTime := time .Now ()
200
- logger .Info (ctx ,"waiting for notifications" ,slog .F ("username" ,user .Username ))
193
+ // watchNotifications reads notifications from the websocket and returns error or nil
194
+ // once all expected notifications are received.
195
+ func (r * Runner )watchNotifications (ctx context.Context ,conn * websocket.Conn ,user codersdk.User ,logger slog.Logger ,expectedNotifications map [uuid.UUID ]chan time.Time )error {
196
+ logger .Info (ctx ,"waiting for notifications" ,
197
+ slog .F ("username" ,user .Username ),
198
+ slog .F ("expected_count" ,len (expectedNotifications )))
201
199
202
- receivedCreated := false
203
- receivedDeleted := false
200
+ receivedNotifications := make (map [uuid.UUID ]bool ,len (expectedNotifications ))
201
+ for templateID := range expectedNotifications {
202
+ receivedNotifications [templateID ]= false
203
+ }
204
+
205
+ for {
206
+ select {
207
+ case <- ctx .Done ():
208
+ return xerrors .Errorf ("context canceled while waiting for notifications: %w" ,ctx .Err ())
209
+ default :
210
+ }
211
+
212
+ allReceived := true
213
+ for _ ,received := range receivedNotifications {
214
+ if ! received {
215
+ allReceived = false
216
+ break
217
+ }
218
+ }
219
+ if allReceived {
220
+ logger .Info (ctx ,"received all expected notifications" )
221
+ return nil
222
+ }
204
223
205
- // Read notifications until we have both expected types
206
- for ! receivedCreated || ! receivedDeleted {
207
224
notif ,err := readNotification (ctx ,conn )
208
225
if err != nil {
209
226
logger .Error (ctx ,"read notification" ,slog .Error (err ))
210
227
r .cfg .Metrics .AddError (user .Username ,"read_notification" )
211
228
return xerrors .Errorf ("read notification: %w" ,err )
212
229
}
213
230
214
- switch notif .Notification .TemplateID {
215
- case notifications .TemplateUserAccountCreated :
216
- if ! receivedCreated {
217
- r .userCreatedNotificationLatency = time .Since (notificationStartTime )
218
- r .cfg .Metrics .RecordLatency (r .userCreatedNotificationLatency ,user .Username ,"user_created" )
219
- receivedCreated = true
220
- logger .Info (ctx ,"received user created notification" )
221
- }
222
- case notifications .TemplateUserAccountDeleted :
223
- if ! receivedDeleted {
224
- r .userDeletedNotificationLatency = time .Since (notificationStartTime )
225
- r .cfg .Metrics .RecordLatency (r .userDeletedNotificationLatency ,user .Username ,"user_deleted" )
226
- receivedDeleted = true
227
- logger .Info (ctx ,"received user deleted notification" )
231
+ templateID := notif .Notification .TemplateID
232
+ if triggerTimeChan ,exists := expectedNotifications [templateID ];exists {
233
+ if ! receivedNotifications [templateID ] {
234
+ select {
235
+ case triggerTime := <- triggerTimeChan :
236
+ latency := time .Since (triggerTime )
237
+ r .notificationLatencies [templateID ]= latency
238
+ r .cfg .Metrics .RecordLatency (latency ,user .Username ,templateID .String ())
239
+ receivedNotifications [templateID ]= true
240
+
241
+ logger .Info (ctx ,"received expected notification" ,
242
+ slog .F ("template_id" ,templateID ),
243
+ slog .F ("title" ,notif .Notification .Title ),
244
+ slog .F ("latency" ,latency ))
245
+ case <- ctx .Done ():
246
+ return xerrors .Errorf ("context canceled while waiting for trigger time: %w" ,ctx .Err ())
247
+ }
228
248
}
229
- default :
230
- logger .Warn (ctx ,"receivedunexpected notificationtype " ,
231
- slog .F ("template_id" ,notif . Notification . TemplateID ),
249
+ } else {
250
+ logger .Debug (ctx ,"received notificationnot being tested " ,
251
+ slog .F ("template_id" ,templateID ),
232
252
slog .F ("title" ,notif .Notification .Title ))
233
253
}
234
254
}
235
- logger .Info (ctx ,"received both notifications successfully" )
236
- return nil
237
255
}
238
256
239
257
func readNotification (ctx context.Context ,conn * websocket.Conn ) (codersdk.GetInboxNotificationResponse ,error ) {