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

Commit2e51056

Browse files
committed
add test for latencies
1 parent2198c5f commit2e51056

File tree

8 files changed

+316
-30
lines changed

8 files changed

+316
-30
lines changed

‎coderd/database/dbfake/dbfake.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2188,7 +2188,66 @@ func (q *FakeQuerier) GetUserLatencyInsights(ctx context.Context, arg database.G
21882188
returnnil,err
21892189
}
21902190

2191-
panic("not implemented")
2191+
q.mutex.RLock()
2192+
deferq.mutex.RUnlock()
2193+
2194+
latenciesByUserID:=make(map[uuid.UUID][]float64)
2195+
seenTemplatesByUserID:=make(map[uuid.UUID]map[uuid.UUID]struct{})
2196+
for_,s:=rangeq.workspaceAgentStats {
2197+
iflen(arg.TemplateIDs)>0&&!slices.Contains(arg.TemplateIDs,s.TemplateID) {
2198+
continue
2199+
}
2200+
if!arg.StartTime.Equal(s.CreatedAt)&&!(s.CreatedAt.After(arg.StartTime)&&s.CreatedAt.Before(arg.EndTime)) {
2201+
continue
2202+
}
2203+
ifs.ConnectionCount==0 {
2204+
continue
2205+
}
2206+
2207+
latenciesByUserID[s.UserID]=append(latenciesByUserID[s.UserID],s.ConnectionMedianLatencyMS)
2208+
ifseenTemplatesByUserID[s.UserID]==nil {
2209+
seenTemplatesByUserID[s.UserID]=make(map[uuid.UUID]struct{})
2210+
}
2211+
seenTemplatesByUserID[s.UserID][s.TemplateID]=struct{}{}
2212+
}
2213+
2214+
tryPercentile:=func(fs []float64,pfloat64)float64 {
2215+
iflen(fs)==0 {
2216+
return-1
2217+
}
2218+
sort.Float64s(fs)
2219+
returnfs[int(float64(len(fs))*p/100)]
2220+
}
2221+
2222+
varrows []database.GetUserLatencyInsightsRow
2223+
foruserID,latencies:=rangelatenciesByUserID {
2224+
sort.Float64s(latencies)
2225+
templateSet:=seenTemplatesByUserID[userID]
2226+
templateIDs:=make([]uuid.UUID,0,len(templateSet))
2227+
fortemplateID:=rangetemplateSet {
2228+
templateIDs=append(templateIDs,templateID)
2229+
}
2230+
slices.SortFunc(templateIDs,func(a,b uuid.UUID)bool {
2231+
returna.String()<b.String()
2232+
})
2233+
user,err:=q.getUserByIDNoLock(userID)
2234+
iferr!=nil {
2235+
returnnil,err
2236+
}
2237+
row:= database.GetUserLatencyInsightsRow{
2238+
UserID:userID,
2239+
Username:user.Username,
2240+
TemplateIDs:templateIDs,
2241+
WorkspaceConnectionLatency50:tryPercentile(latencies,50),
2242+
WorkspaceConnectionLatency95:tryPercentile(latencies,95),
2243+
}
2244+
rows=append(rows,row)
2245+
}
2246+
slices.SortFunc(rows,func(a,b database.GetUserLatencyInsightsRow)bool {
2247+
returna.UserID.String()<b.UserID.String()
2248+
})
2249+
2250+
returnrows,nil
21922251
}
21932252

21942253
func (q*FakeQuerier)GetUserLinkByLinkedID(_ context.Context,idstring) (database.UserLink,error) {

‎coderd/database/queries.sql.go

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/queries/insights.sql

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,21 @@ WITH d AS (
4747

4848
SELECT
4949
COUNT(DISTINCT user_id)AS active_users,
50-
array_agg(DISTINCT template_id)::uuid[]AS template_ids,
51-
SUM(usage_vscode_seconds)AS usage_vscode_seconds,
52-
SUM(usage_jetbrains_seconds)AS usage_jetbrains_seconds,
53-
SUM(usage_reconnecting_pty_seconds)AS usage_reconnecting_pty_seconds,
54-
SUM(usage_ssh_seconds)AS usage_ssh_seconds
50+
COALESCE(array_agg(DISTINCT template_id),'{}')::uuid[]AS template_ids,
51+
COALESCE(SUM(usage_vscode_seconds),0)::bigintAS usage_vscode_seconds,
52+
COALESCE(SUM(usage_jetbrains_seconds),0)::bigintAS usage_jetbrains_seconds,
53+
COALESCE(SUM(usage_reconnecting_pty_seconds),0)::bigintAS usage_reconnecting_pty_seconds,
54+
COALESCE(SUM(usage_ssh_seconds),0)::bigintAS usage_ssh_seconds
5555
FROM usage_by_user, unnest(template_ids)as template_id;
5656

5757
-- name: GetTemplateDailyInsights :many
5858
WITH dAS (
59+
-- sqlc workaround, use SELECT generate_series instead of SELECT * FROM generate_series.
5960
SELECT generate_series(@start_time::timestamptz, @end_time::timestamptz,'1 day'::interval)AS d
6061
), tsAS (
6162
SELECT
6263
d::timestamptzAS from_,
63-
(d+'1 day'::interval)::timestamptzAS to_
64+
CASE WHEN(d+'1 day'::interval)::timestamptz<= @end_time::timestamptz THEN (d+'1 day'::interval)::timestamptz ELSE @end_time::timestamptz ENDAS to_
6465
FROM d
6566
), usage_by_dayAS (
6667
SELECT

‎coderd/insights.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,6 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
307307
// clock must be set to 00:00:00, except for "today", where end time is allowed
308308
// to provide the hour of the day (e.g. 14:00:00).
309309
funcparseInsightsStartAndEndTime(ctx context.Context,rw http.ResponseWriter,startTimeString,endTimeStringstring) (startTime,endTime time.Time,okbool) {
310-
constinsightsTimeLayout=time.RFC3339Nano
311310
now:=time.Now()
312311

313312
for_,qp:=range []struct {
@@ -317,14 +316,14 @@ func parseInsightsStartAndEndTime(ctx context.Context, rw http.ResponseWriter, s
317316
{"start_time",startTimeString,&startTime},
318317
{"end_time",endTimeString,&endTime},
319318
} {
320-
t,err:=time.Parse(insightsTimeLayout,qp.value)
319+
t,err:=time.Parse(codersdk.InsightsTimeLayout,qp.value)
321320
iferr!=nil {
322321
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
323322
Message:"Query parameter has invalid value.",
324323
Validations: []codersdk.ValidationError{
325324
{
326325
Field:qp.name,
327-
Detail:fmt.Sprintf("Query param %q must be a valid date format (%s): %s",qp.name,insightsTimeLayout,err.Error()),
326+
Detail:fmt.Sprintf("Query param %q must be a valid date format (%s): %s",qp.name,codersdk.InsightsTimeLayout,err.Error()),
328327
},
329328
},
330329
})
@@ -344,7 +343,8 @@ func parseInsightsStartAndEndTime(ctx context.Context, rw http.ResponseWriter, s
344343
return time.Time{}, time.Time{},false
345344
}
346345

347-
ift.After(now) {
346+
// Round upwards one hour to ensure we can fetch the latest data.
347+
ift.After(now.Truncate(time.Hour).Add(time.Hour)) {
348348
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
349349
Message:"Query parameter has invalid value.",
350350
Validations: []codersdk.ValidationError{

‎coderd/insights_internal_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package coderd
2+
3+
import (
4+
"context"
5+
"net/http/httptest"
6+
"testing"
7+
"time"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/coder/coder/codersdk"
13+
)
14+
15+
funcTest_parseInsightsStartAndEndTime(t*testing.T) {
16+
t.Parallel()
17+
18+
format:=codersdk.InsightsTimeLayout
19+
now:=time.Now().UTC()
20+
y,m,d:=now.Date()
21+
today:=time.Date(y,m,d,0,0,0,0,time.UTC)
22+
thisHour:=time.Date(y,m,d,now.Hour(),0,0,0,time.UTC)
23+
thisHourRoundUp:=thisHour.Add(time.Hour)
24+
25+
helsinki,err:=time.LoadLocation("Europe/Helsinki")
26+
require.NoError(t,err)
27+
28+
typeargsstruct {
29+
startTimestring
30+
endTimestring
31+
}
32+
tests:= []struct {
33+
namestring
34+
argsargs
35+
wantStartTime time.Time
36+
wantEndTime time.Time
37+
wantOkbool
38+
}{
39+
{
40+
name:"Week",
41+
args:args{
42+
startTime:"2023-07-10T00:00:00Z",
43+
endTime:"2023-07-17T00:00:00Z",
44+
},
45+
wantStartTime:time.Date(2023,7,10,0,0,0,0,time.UTC),
46+
wantEndTime:time.Date(2023,7,17,0,0,0,0,time.UTC),
47+
wantOk:true,
48+
},
49+
{
50+
name:"Today",
51+
args:args{
52+
startTime:today.Format(format),
53+
endTime:thisHour.Format(format),
54+
},
55+
wantStartTime:time.Date(2023,7,today.Day(),0,0,0,0,time.UTC),
56+
wantEndTime:time.Date(2023,7,today.Day(),thisHour.Hour(),0,0,0,time.UTC),
57+
wantOk:true,
58+
},
59+
{
60+
name:"Today with minutes and seconds",
61+
args:args{
62+
startTime:today.Format(format),
63+
endTime:thisHour.Add(time.Minute+time.Second).Format(format),
64+
},
65+
wantOk:false,
66+
},
67+
{
68+
name:"Today (hour round up)",
69+
args:args{
70+
startTime:today.Format(format),
71+
endTime:thisHourRoundUp.Format(format),
72+
},
73+
wantStartTime:time.Date(2023,7,today.Day(),0,0,0,0,time.UTC),
74+
wantEndTime:time.Date(2023,7,today.Day(),thisHourRoundUp.Hour(),0,0,0,time.UTC),
75+
wantOk:true,
76+
},
77+
{
78+
name:"Other timezone week",
79+
args:args{
80+
startTime:"2023-07-10T00:00:00+03:00",
81+
endTime:"2023-07-17T00:00:00+03:00",
82+
},
83+
wantStartTime:time.Date(2023,7,10,0,0,0,0,helsinki),
84+
wantEndTime:time.Date(2023,7,17,0,0,0,0,helsinki),
85+
wantOk:true,
86+
},
87+
{
88+
name:"Bad format",
89+
args:args{
90+
startTime:"2023-07-10",
91+
endTime:"2023-07-17",
92+
},
93+
wantOk:false,
94+
},
95+
{
96+
name:"Zero time",
97+
args:args{
98+
startTime: (time.Time{}).Format(format),
99+
endTime: (time.Time{}).Format(format),
100+
},
101+
wantOk:false,
102+
},
103+
{
104+
name:"Time in future",
105+
args:args{
106+
startTime:today.AddDate(0,0,1).Format(format),
107+
endTime:today.AddDate(0,0,2).Format(format),
108+
},
109+
wantOk:false,
110+
},
111+
{
112+
name:"End before start",
113+
args:args{
114+
startTime:today.Format(format),
115+
endTime:today.AddDate(0,0,-1).Format(format),
116+
},
117+
wantOk:false,
118+
},
119+
}
120+
for_,tt:=rangetests {
121+
tt:=tt
122+
t.Run(tt.name,func(t*testing.T) {
123+
t.Parallel()
124+
125+
rw:=httptest.NewRecorder()
126+
gotStartTime,gotEndTime,gotOk:=parseInsightsStartAndEndTime(context.Background(),rw,tt.args.startTime,tt.args.endTime)
127+
128+
// assert.Equal is unable to test location equality, so we
129+
// use assert.WithinDuration.
130+
assert.WithinDuration(t,tt.wantStartTime,gotStartTime,0)
131+
assert.True(t,tt.wantStartTime.Equal(gotStartTime))
132+
assert.WithinDuration(t,tt.wantEndTime,gotEndTime,0)
133+
assert.True(t,tt.wantEndTime.Equal(gotEndTime))
134+
assert.Equal(t,tt.wantOk,gotOk)
135+
})
136+
}
137+
}

‎coderd/insights_test.go

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ func TestUserLatencyInsights(t *testing.T) {
112112
})
113113

114114
user:=coderdtest.CreateFirstUser(t,client)
115+
_,user2:=coderdtest.CreateAnotherUser(t,client,user.OrganizationID)
115116
authToken:=uuid.NewString()
116117
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,&echo.Responses{
117118
Parse:echo.ParseComplete,
@@ -134,15 +135,54 @@ func TestUserLatencyInsights(t *testing.T) {
134135
deferfunc() {
135136
_=agentCloser.Close()
136137
}()
137-
_=coderdtest.AwaitWorkspaceAgents(t,client,workspace.ID)
138+
resources:=coderdtest.AwaitWorkspaceAgents(t,client,workspace.ID)
138139

139140
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
140141
defercancel()
141142

142-
userLatencies,err:=client.UserLatencyInsights(ctx)
143+
conn,err:=client.DialWorkspaceAgent(ctx,resources[0].Agents[0].ID,&codersdk.DialWorkspaceAgentOptions{
144+
Logger:logger.Named("client"),
145+
})
146+
require.NoError(t,err)
147+
deferconn.Close()
148+
149+
sshConn,err:=conn.SSHClient(ctx)
143150
require.NoError(t,err)
151+
defersshConn.Close()
144152

145-
t.Logf("%#v\n",userLatencies)
153+
// Create users that will not appear in the report.
154+
_,user3:=coderdtest.CreateAnotherUser(t,client,user.OrganizationID)
155+
_,user4:=coderdtest.CreateAnotherUser(t,client,user.OrganizationID)
156+
_,err=client.UpdateUserStatus(ctx,user3.Username,codersdk.UserStatusSuspended)
157+
require.NoError(t,err)
158+
err=client.DeleteUser(ctx,user4.ID)
159+
require.NoError(t,err)
160+
161+
y,m,d:=time.Now().Date()
162+
today:=time.Date(y,m,d,0,0,0,0,time.UTC)
163+
164+
_=sshConn.Close()
165+
166+
varuserLatencies codersdk.UserLatencyInsightsResponse
167+
require.Eventuallyf(t,func()bool {
168+
userLatencies,err=client.UserLatencyInsights(ctx, codersdk.UserLatencyInsightsRequest{
169+
StartTime:today,
170+
EndTime:time.Now().UTC().Truncate(time.Hour).Add(time.Hour),// Round up to include the current hour.
171+
TemplateIDs: []uuid.UUID{template.ID},
172+
})
173+
if!assert.NoError(t,err) {
174+
returnfalse
175+
}
176+
ifuserLatencies.Report.Users[0].UserID==user2.ID {
177+
userLatencies.Report.Users[0],userLatencies.Report.Users[1]=userLatencies.Report.Users[1],userLatencies.Report.Users[0]
178+
}
179+
returnuserLatencies.Report.Users[0].LatencyMS!=nil
180+
},testutil.WaitShort,testutil.IntervalFast,"user latency is missing")
181+
182+
require.Len(t,userLatencies.Report.Users,2,"only 2 users should be included")
183+
assert.Greater(t,userLatencies.Report.Users[0].LatencyMS.P50,float64(0),"expected p50 to be greater than 0")
184+
assert.Greater(t,userLatencies.Report.Users[0].LatencyMS.P95,float64(0),"expected p95 to be greater than 0")
185+
assert.Nil(t,userLatencies.Report.Users[1].LatencyMS,"user 2 should have no latency")
146186
}
147187

148188
funcTestTemplateInsights(t*testing.T) {
@@ -183,7 +223,11 @@ func TestTemplateInsights(t *testing.T) {
183223
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
184224
defercancel()
185225

186-
templateInsights,err:=client.TemplateInsights(ctx)
226+
templateInsights,err:=client.TemplateInsights(ctx, codersdk.TemplateInsightsRequest{
227+
StartTime:time.Now().Add(-time.Hour),
228+
EndTime:time.Now(),
229+
Interval:codersdk.InsightsReportIntervalDay,
230+
})
187231
require.NoError(t,err)
188232

189233
t.Logf("%#v\n",templateInsights)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp