Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3db0798

Browse files
committed
add ExpectedNotifications map with channel to accurately measure latency
1 parentbfd3cda commit3db0798

File tree

3 files changed

+102
-60
lines changed

3 files changed

+102
-60
lines changed

‎scaletest/notifications/config.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ type Config struct {
2525
// DialTimeout is how long to wait for websocket connection.
2626
DialTimeout time.Duration`json:"dial_timeout"`
2727

28+
// ExpectedNotifications maps notification template IDs to channels
29+
// that receive the trigger time for each notification.
30+
ExpectedNotificationsmap[uuid.UUID]chan time.Time`json:"-"`
31+
2832
Metrics*Metrics`json:"-"`
2933

3034
// DialBarrier ensures all runners are connected before notifications are triggered.
@@ -52,6 +56,10 @@ func (c Config) Validate() error {
5256
returnxerrors.New("owner_dial_barrier must be set for regular users")
5357
}
5458

59+
ifc.IsOwner&&len(c.ExpectedNotifications)==0 {
60+
returnxerrors.New("expected_notifications must be set for owner users")
61+
}
62+
5563
ifc.NotificationTimeout<=0 {
5664
returnxerrors.New("notification_timeout must be greater than 0")
5765
}

‎scaletest/notifications/run.go‎

Lines changed: 63 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import (
88
"net/http"
99
"time"
1010

11+
"github.com/google/uuid"
1112
"golang.org/x/xerrors"
1213

1314
"cdr.dev/slog"
1415
"cdr.dev/slog/sloggers/sloghuman"
1516

16-
"github.com/coder/coder/v2/coderd/notifications"
1717
"github.com/coder/coder/v2/coderd/tracing"
1818
"github.com/coder/coder/v2/codersdk"
1919
"github.com/coder/coder/v2/scaletest/createusers"
@@ -28,14 +28,15 @@ type Runner struct {
2828

2929
createUserRunner*createusers.Runner
3030

31-
userCreatedNotificationLatency time.Duration
32-
userDeletedNotificationLatencytime.Duration
31+
// notificationLatencies stores the latency for each notification type
32+
notificationLatenciesmap[uuid.UUID]time.Duration
3333
}
3434

3535
funcNewRunner(client*codersdk.Client,cfgConfig)*Runner {
3636
return&Runner{
37-
client:client,
38-
cfg:cfg,
37+
client:client,
38+
cfg:cfg,
39+
notificationLatencies:make(map[uuid.UUID]time.Duration),
3940
}
4041
}
4142

@@ -73,7 +74,7 @@ func (r *Runner) Run(ctx context.Context, id string, logs io.Writer) error {
7374
codersdk.WithLogger(logger),
7475
codersdk.WithLogBodies())
7576

76-
logger.Info(ctx,"user created",slog.F("username",newUser.Username),slog.F("user_id",newUser.ID.String()))
77+
logger.Info(ctx,"runneruser created",slog.F("username",newUser.Username),slog.F("user_id",newUser.ID.String()))
7778

7879
ifr.cfg.IsOwner {
7980
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 {
128129
watchCtx,cancel:=context.WithTimeout(ctx,r.cfg.NotificationTimeout)
129130
defercancel()
130131

131-
iferr:=r.watchNotifications(watchCtx,conn,newUser,logger);err!=nil {
132+
iferr:=r.watchNotifications(watchCtx,conn,newUser,logger,r.cfg.ExpectedNotifications);err!=nil {
132133
returnxerrors.Errorf("notification watch failed: %w",err)
133134
}
134135

@@ -146,20 +147,16 @@ func (r *Runner) Cleanup(ctx context.Context, id string, logs io.Writer) error {
146147
returnnil
147148
}
148149

149-
const (
150-
UserCreatedNotificationLatencyMetric="user_created_notification_latency_seconds"
151-
UserDeletedNotificationLatencyMetric="user_deleted_notification_latency_seconds"
152-
)
150+
constNotificationDeliveryLatencyMetric="notification_delivery_latency_seconds"
153151

154152
func (r*Runner)GetMetrics()map[string]any {
155153
metrics:=map[string]any{}
156154

157-
ifr.userCreatedNotificationLatency>0 {
158-
metrics[UserCreatedNotificationLatencyMetric]=r.userCreatedNotificationLatency.Seconds()
159-
}
160-
161-
ifr.userDeletedNotificationLatency>0 {
162-
metrics[UserDeletedNotificationLatencyMetric]=r.userDeletedNotificationLatency.Seconds()
155+
fortemplateID,latency:=ranger.notificationLatencies {
156+
iflatency>0 {
157+
metricKey:=fmt.Sprintf("%s_%s",NotificationDeliveryLatencyMetric,templateID.String())
158+
metrics[metricKey]=latency.Seconds()
159+
}
163160
}
164161

165162
returnmetrics
@@ -193,47 +190,68 @@ func (r *Runner) dialNotificationWebsocket(ctx context.Context, client *codersdk
193190
returnconn,nil
194191
}
195192

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,expectedNotificationsmap[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)))
201199

202-
receivedCreated:=false
203-
receivedDeleted:=false
200+
receivedNotifications:=make(map[uuid.UUID]bool,len(expectedNotifications))
201+
fortemplateID:=rangeexpectedNotifications {
202+
receivedNotifications[templateID]=false
203+
}
204+
205+
for {
206+
select {
207+
case<-ctx.Done():
208+
returnxerrors.Errorf("context canceled while waiting for notifications: %w",ctx.Err())
209+
default:
210+
}
211+
212+
allReceived:=true
213+
for_,received:=rangereceivedNotifications {
214+
if!received {
215+
allReceived=false
216+
break
217+
}
218+
}
219+
ifallReceived {
220+
logger.Info(ctx,"received all expected notifications")
221+
returnnil
222+
}
204223

205-
// Read notifications until we have both expected types
206-
for!receivedCreated||!receivedDeleted {
207224
notif,err:=readNotification(ctx,conn)
208225
iferr!=nil {
209226
logger.Error(ctx,"read notification",slog.Error(err))
210227
r.cfg.Metrics.AddError(user.Username,"read_notification")
211228
returnxerrors.Errorf("read notification: %w",err)
212229
}
213230

214-
switchnotif.Notification.TemplateID {
215-
casenotifications.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-
casenotifications.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+
iftriggerTimeChan,exists:=expectedNotifications[templateID];exists {
233+
if!receivedNotifications[templateID] {
234+
select {
235+
casetriggerTime:=<-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+
returnxerrors.Errorf("context canceled while waiting for trigger time: %w",ctx.Err())
247+
}
228248
}
229-
default:
230-
logger.Warn(ctx,"receivedunexpectednotificationtype",
231-
slog.F("template_id",notif.Notification.TemplateID),
249+
}else {
250+
logger.Debug(ctx,"received notificationnot being tested",
251+
slog.F("template_id",templateID),
232252
slog.F("title",notif.Notification.Title))
233253
}
234254
}
235-
logger.Info(ctx,"received both notifications successfully")
236-
returnnil
237255
}
238256

239257
funcreadNotification(ctx context.Context,conn*websocket.Conn) (codersdk.GetInboxNotificationResponse,error) {

‎scaletest/notifications/run_test.go‎

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package notifications_test
33
import (
44
"io"
55
"strconv"
6+
"strings"
67
"sync"
78
"testing"
89
"time"
@@ -82,18 +83,24 @@ func TestRun(t *testing.T) {
8283

8384
eg,runCtx:=errgroup.WithContext(ctx)
8485

86+
expectedNotifications:=map[uuid.UUID]chan time.Time{
87+
notificationsLib.TemplateUserAccountCreated:make(chan time.Time,1),
88+
notificationsLib.TemplateUserAccountDeleted:make(chan time.Time,1),
89+
}
90+
8591
// Start owner runners who will receive notifications
8692
ownerRunners:=make([]*notifications.Runner,0,numOwners)
8793
fori:=rangenumOwners {
8894
runnerCfg:= notifications.Config{
8995
User: createusers.Config{
9096
OrganizationID:firstUser.OrganizationID,
9197
},
92-
IsOwner:true,
93-
NotificationTimeout:testutil.WaitLong,
94-
DialTimeout:testutil.WaitLong,
95-
Metrics:metrics,
96-
DialBarrier:ownerBarrier,
98+
IsOwner:true,
99+
NotificationTimeout:testutil.WaitLong,
100+
DialTimeout:testutil.WaitLong,
101+
Metrics:metrics,
102+
DialBarrier:ownerBarrier,
103+
ExpectedNotifications:expectedNotifications,
97104
}
98105
err:=runnerCfg.Validate()
99106
require.NoError(t,err)
@@ -135,6 +142,7 @@ func TestRun(t *testing.T) {
135142
ownerBarrier.Wait()
136143
regularBarrier.Wait()
137144

145+
createTime:=time.Now()
138146
newUser,err:=client.CreateUserWithOrgs(runCtx, codersdk.CreateUserRequestWithOrgs{
139147
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
140148
Email:"test-user@coder.com",
@@ -144,10 +152,16 @@ func TestRun(t *testing.T) {
144152
iferr!=nil {
145153
returnxerrors.Errorf("create test user: %w",err)
146154
}
155+
expectedNotifications[notificationsLib.TemplateUserAccountCreated]<-createTime
147156

157+
deleteTime:=time.Now()
148158
iferr:=client.DeleteUser(runCtx,newUser.ID);err!=nil {
149159
returnxerrors.Errorf("delete test user: %w",err)
150160
}
161+
expectedNotifications[notificationsLib.TemplateUserAccountDeleted]<-deleteTime
162+
163+
close(expectedNotifications[notificationsLib.TemplateUserAccountCreated])
164+
close(expectedNotifications[notificationsLib.TemplateUserAccountDeleted])
151165

152166
returnnil
153167
})
@@ -174,18 +188,20 @@ func TestRun(t *testing.T) {
174188
require.Len(t,users.Users,1)
175189
require.Equal(t,firstUser.UserID,users.Users[0].ID)
176190

177-
// Verify that owner runners received both notifications and recorded metrics
178191
for_,runner:=rangeownerRunners {
179192
runnerMetrics:=runner.GetMetrics()
180-
require.Contains(t,runnerMetrics,notifications.UserCreatedNotificationLatencyMetric)
181-
require.Contains(t,runnerMetrics,notifications.UserDeletedNotificationLatencyMetric)
182-
}
183-
184-
// Verify that regular runners don't have notification metrics
185-
for_,runner:=rangeregularRunners {
186-
runnerMetrics:=runner.GetMetrics()
187-
require.NotContains(t,runnerMetrics,notifications.UserCreatedNotificationLatencyMetric)
188-
require.NotContains(t,runnerMetrics,notifications.UserDeletedNotificationLatencyMetric)
193+
foundCreated:=false
194+
foundDeleted:=false
195+
forkey:=rangerunnerMetrics {
196+
ifstrings.Contains(key,notificationsLib.TemplateUserAccountCreated.String()) {
197+
foundCreated=true
198+
}
199+
ifstrings.Contains(key,notificationsLib.TemplateUserAccountDeleted.String()) {
200+
foundDeleted=true
201+
}
202+
}
203+
require.True(t,foundCreated)
204+
require.True(t,foundDeleted)
189205
}
190206
}
191207

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp