8000 feat(coderd): add user latency and template insights endpoints by mafredri · Pull Request #8519 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat(coderd): add user latency and template insights endpoints #8519

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8a1d3ee
feat(coderd): add user latency and template insights endpoints
mafredri Jul 14, 2023
b31ec33
feat(coderd): implement user latency insights logic
mafredri Jul 18, 2023
aaadc6a
feat(coderd): implement template insights logic
mafredri Jul 19, 2023
b54b0aa
make start/endtime check more strict
mafredri Jul 19, 2023
5f2bfd4
move interval verification
mafredri Jul 19, 2023
4820c53
gen
mafredri Jul 20, 2023
6326643
allow endtime for today include the hour
mafredri Jul 20, 2023
2198c5f
prevent timetravel
mafredri Jul 20, 2023
2e51056
add test for latencies
mafredri Jul 20, 2023
ddc8606
add test for template insights
mafredri Jul 20, 2023
26e0f02
Merge branch 'main' into mafredri/feat-coderd-add-user-latency-and-in…
mafredri Jul 20, 2023
2c8e311
verify same timezone for start/end
mafredri Jul 20, 2023
917997b
fix typo in query comment and improve comments
mafredri Jul 21, 2023
e5b96d4
fix generic internal server error
mafredri Jul 21, 2023
87b1b90
s/greater/after/
mafredri Jul 21, 2023
bffc673
unexport insights time layout
mafredri Jul 21, 2023
056918e
remove interval none
mafredri Jul 21, 2023
d5a9865
remove mixed tz restrictions, test DST
mafredri Jul 21, 2023
088620e
fix lint
mafredri Jul 21, 2023
397cc7a
add fixmes to dbauthz
mafredri Jul 21, 2023
ebacfe1
improve comment about assert equality
mafredri Jul 21, 2023
cd07478
replace sleep with cat
mafredri Jul 21, 2023
040390d
add bad request tests
mafredri Jul 21, 2023
b1de87f
fix comment placement
mafredri Jul 21, 2023
5588e39
create convert function for builtin apps
mafredri Jul 21, 2023
dafbba1
move interval loop
mafredri Jul 21, 2023
fc2157d
remove all users, improve test comments
mafredri Jul 21, 2023
7fd13df
Merge branch 'main' into mafredri/feat-coderd-add-user-latency-and-in…
mafredri Jul 21, 2023
9e228f6
Merge branch 'main' into mafredri/feat-coderd-add-user-latency-and-in…
mafredri Jul 21, 2023
b03cc29
fix windows test?
mafredri Jul 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
remove all users, improve test comments
  • Loading branch information
mafredri committed Jul 21, 2023
commit fc2157d7ee0e777dd247ea661607becaeb384b9a
37 changes: 4 additions & 33 deletions coderd/insights.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,50 +107,21 @@ func (api *API) insightsUserLatency(rw http.ResponseWriter, r *http.Request) {
return
}

// Fetch all users so that we can still include users that have no
// latency data.
users, err := api.Database.GetUsers(ctx, database.GetUsersParams{})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching users.",
Detail: err.Error(),
})
return
}

templateIDSet := make(map[uuid.UUID]struct{})
usersWithLatencyByID := make(map[uuid.UUID]codersdk.UserLatency)
userLatencies := make([]codersdk.UserLatency, 0, len(rows))
for _, row := range rows {
for _, templateID := range row.TemplateIDs {
templateIDSet[templateID] = struct{}{}
}
usersWithLatencyByID[row.UserID] = codersdk.UserLatency{
userLatencies = append(userLatencies, codersdk.UserLatency{
TemplateIDs: row.TemplateIDs,
UserID: row.UserID,
Username: row.Username,
LatencyMS: &codersdk.ConnectionLatency{
LatencyMS: codersdk.ConnectionLatency{
P50: row.WorkspaceConnectionLatency50,
P95: row.WorkspaceConnectionLatency95,
},
}
}
userLatencies := []codersdk.UserLatency{}
for _, user := range users {
userLatency, ok := usersWithLatencyByID[user.ID]
if !ok {
// We only include deleted/inactive users if they were
// active as part of the requested timeframe.
if user.Deleted || user.Status != database.UserStatusActive {
continue
}

userLatency = codersdk.UserLatency{
TemplateIDs: []uuid.UUID{},
UserID: user.ID,
Username: user.Username,
}
}
userLatencies = append(userLatencies, userLatency)
})
}

// TemplateIDs that contributed to the data.
Expand Down
52 changes: 27 additions & 25 deletions coderd/insights_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@ func TestUserLatencyInsights(t *testing.T) {
AgentStatsRefreshInterval: time.Millisecond * 100,
})

// Create two users, one that will appear in the report and another that
// won't (due to not having/using a workspace).
user := coderdtest.CreateFirstUser(t, client)
_, user2 := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
_, _ = coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Expand All @@ -127,6 +129,7 @@ func TestUserLatencyInsights(t *testing.T) {
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)

// Start an agent so that we can generate stats.
agentClient := agentsdk.New(client.URL)
agentClient.SetSessionToken(authToken)
agentCloser := agent.New(agent.Options{
Expand All @@ -138,9 +141,15 @@ func TestUserLatencyInsights(t *testing.T) {
}()
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)

// Start must be at the beginning of the day, initialize it early in case
// the day changes so that we get the relevant stats faster.
y, m, d := time.Now().UTC().Date()
today := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

// Connect to the agent to generate usage/latency stats.
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
Logger: logger.Named("client"),
})
Expand All @@ -151,19 +160,6 @@ func TestUserLatencyInsights(t *testing.T) {
require.NoError(t, err)
defer sshConn.Close()

// Create users that will not appear in the report.
_, user3 := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
_, user4 := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
_, err = client.UpdateUserStatus(ctx, user3.Username, codersdk.UserStatusSuspended)
require.NoError(t, err)
err = client.DeleteUser(ctx, user4.ID)
require.NoError(t, err)

y, m, d := time.Now().Date()
today := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)

_ = sshConn.Close()

var userLatencies codersdk.UserLatencyInsightsResponse
require.Eventuallyf(t, func() bool {
userLatencies, err = client.UserLatencyInsights(ctx, codersdk.UserLatencyInsightsRequest{
Expand All @@ -174,16 +170,16 @@ func TestUserLatencyInsights(t *testing.T) {
if !assert.NoError(t, err) {
return false
}
if userLatencies.Report.Users[0].UserID == user2.ID {
userLatencies.Report.Users[0], userLatencies.Report.Users[1] = userLatencies.Report.Users[1], userLatencies.Report.Users[0]
}
return userLatencies.Report.Users[0].LatencyMS != nil
return len(userLatencies.Report.Users) > 0 && userLatencies.Report.Users[0].LatencyMS.P50 > 0
}, testutil.WaitShort, testutil.IntervalFast, "user latency is missing")

require.Len(t, userLatencies.Report.Users, 2, "want only 2 users")
// We got our latency data, close the connection.
_ = sshConn.Close()

require.Len(t, userLatencies.Report.Users, 1, "want only 1 user")
require.Equal(t, userLatencies.Report.Users[0].UserID, user.UserID, "want user id to match")
assert.Greater(t, userLatencies.Report.Users[0].LatencyMS.P50, float64(0), "want p50 to be greater than 0")
assert.Greater(t, userLatencies.Report.Users[0].LatencyMS.P95, float64(0), "want p95 to be greater than 0")
assert.Nil(t, userLatencies.Report.Users[1].LatencyMS, "want user 2 to have no latency")
}

func TestUserLatencyInsights_BadRequest(t *testing.T) {
Expand All @@ -192,7 +188,7 @@ func TestUserLatencyInsights_BadRequest(t *testing.T) {
client := coderdtest.New(t, &coderdtest.Options{})
_ = coderdtest.CreateFirstUser(t, client)

y, m, d := time.Now().Date()
y, m, d := time.Now().UTC().Date()
today := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
Expand Down Expand Up @@ -235,6 +231,7 @@ func TestTemplateInsights(t *testing.T) {
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)

// Start an agent so that we can generate stats.
agentClient := agentsdk.New(client.URL)
agentClient.SetSessionToken(authToken)
agentCloser := agent.New(agent.Options{
Expand All @@ -246,12 +243,15 @@ func TestTemplateInsights(t *testing.T) {
}()
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)

y, m, d := time.Now().Date()
// Start must be at the beginning of the day, initialize it early in case
// the day changes so that we get the relevant stats faster.
y, m, d := time.Now().UTC().Date()
today := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

// Connect to the agent to generate usage/latency stats.
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
Logger: logger.Named("client"),
})
Expand All @@ -262,18 +262,19 @@ func TestTemplateInsights(t *testing.T) {
require.NoError(t, err)
defer sshConn.Close()

// Start an SSH session to generate SSH usage stats.
sess, err := sshConn.NewSession()
require.NoError(t, err)
defer sess.Close()

// Keep SSH session open for long enough to generate insights.
r, w := io.Pipe()
defer r.Close()
defer w.Close()
sess.Stdin = r
err = sess.Start("cat")
require.NoError(t, err)

// Start an rpty session to generate rpty usage stats.
rpty, err := client.WorkspaceAgentReconnectingPTY(ctx, codersdk.WorkspaceAgentReconnectingPTYOpts{
AgentID: resources[0].Agents[0].ID,
Reconnect: uuid.New(),
Expand All @@ -289,7 +290,7 @@ func TestTemplateInsights(t *testing.T) {
return func() bool {
req = codersdk.TemplateInsightsRequest{
StartTime: today,
EndTime: time.Now().Truncate(time.Hour).Add(time.Hour),
EndTime: time.Now().UTC().Truncate(time.Hour).Add(time.Hour),
Interval: codersdk.InsightsReportIntervalDay,
}
resp, err = client.TemplateInsights(ctx, req)
Expand All @@ -308,6 +309,7 @@ func TestTemplateInsights(t *testing.T) {
require.Eventually(t, waitForAppSeconds("reconnecting-pty"), testutil.WaitShort, testutil.IntervalFast, "reconnecting-pty seconds missing")
require.Eventually(t, waitForAppSeconds("ssh"), testutil.WaitShort, testutil.IntervalFast, "ssh seconds missing")

// We got our data, close down sessions and connections.
_ = rpty.Close()
_ = sess.Close()
_ = sshConn.Close()
Expand Down Expand Up @@ -335,7 +337,7 @@ func TestTemplateInsights_BadRequest(t *testing.T) {
client := coderdtest.New(t, &coderdtest.Options{})
_ = coderdtest.CreateFirstUser(t, client)

y, m, d := time.Now().Date()
y, m, d := time.Now().UTC().Date()
today := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
Expand Down
8 changes: 4 additions & 4 deletions codersdk/insights.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ type UserLatencyInsightsReport struct {

// UserLatency shows the connection latency for a user.
type UserLatency struct {
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
UserID uuid.UUID `json:"user_id" format:"uuid"`
Username string `json:"username"`
LatencyMS *ConnectionLatency `json:"latency_ms"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
UserID uuid.UUID `json:"user_id" format:"uuid"`
Username string `json:"username"`
LatencyMS ConnectionLatency `json:"latency_ms"`
}

// ConnectionLatency shows the latency for a connection.
Expand Down
2 changes: 1 addition & 1 deletion site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1169,7 +1169,7 @@ export interface UserLatency {
readonly template_ids: string[]
readonly user_id: string
readonly username: string
readonly latency_ms?: ConnectionLatency
readonly latency_ms: ConnectionLatency
}

// From codersdk/insights.go
Expand Down
0