diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index d2f71f7ea22f0..7bfaa6aa29246 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -2326,12 +2326,15 @@ func (q *FakeQuerier) GetUserLatencyInsights(_ context.Context, arg database.Get if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, s.TemplateID) { continue } - if !arg.StartTime.Equal(s.CreatedAt) && !(s.CreatedAt.After(arg.StartTime) && s.CreatedAt.Before(arg.EndTime)) { + if !arg.StartTime.Equal(s.CreatedAt) && (s.CreatedAt.Before(arg.StartTime) || s.CreatedAt.After(arg.EndTime)) { continue } if s.ConnectionCount == 0 { continue } + if s.ConnectionMedianLatencyMS <= 0 { + continue + } latenciesByUserID[s.UserID] = append(latenciesByUserID[s.UserID], s.ConnectionMedianLatencyMS) if seenTemplatesByUserID[s.UserID] == nil { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e85603b1debdc..841b1ef1dc9d8 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1378,7 +1378,8 @@ func (q *sqlQuerier) UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDPar const getTemplateDailyInsights = `-- name: GetTemplateDailyInsights :many WITH d AS ( -- sqlc workaround, use SELECT generate_series instead of SELECT * FROM generate_series. - SELECT generate_series($1::timestamptz, $2::timestamptz, '1 day'::interval) AS d + -- Subtract 1 second from end_time to avoid including the next interval in the results. + SELECT generate_series($1::timestamptz, ($2::timestamptz) - '1 second'::interval, '1 day'::interval) AS d ), ts AS ( SELECT d::timestamptz AS from_, @@ -1398,17 +1399,20 @@ WITH d AS ( ) GROUP BY ts.from_, ts.to_, was.user_id ), template_ids AS ( - SELECT array_agg(DISTINCT template_id) AS ids + SELECT + from_, + array_agg(DISTINCT template_id) AS ids FROM usage_by_day, unnest(template_ids) template_id WHERE template_id IS NOT NULL + GROUP BY from_, template_ids ) SELECT from_ AS start_time, to_ AS end_time, - COALESCE((SELECT ids FROM template_ids), '{}')::uuid[] AS template_ids, + COALESCE((SELECT template_ids.ids FROM template_ids WHERE template_ids.from_ = usage_by_day.from_), '{}')::uuid[] AS template_ids, COUNT(DISTINCT user_id) AS active_users -FROM usage_by_day, unnest(template_ids) as template_id +FROM usage_by_day GROUP BY from_, to_ ` @@ -1459,7 +1463,8 @@ func (q *sqlQuerier) GetTemplateDailyInsights(ctx context.Context, arg GetTempla const getTemplateInsights = `-- name: GetTemplateInsights :one WITH d AS ( - SELECT generate_series($1::timestamptz, $2::timestamptz, '5 minute'::interval) AS d + -- Subtract 1 second from end_time to avoid including the next interval in the results. + SELECT generate_series($1::timestamptz, ($2::timestamptz) - '1 second'::interval, '5 minute'::interval) AS d ), ts AS ( SELECT d::timestamptz AS from_, diff --git a/coderd/database/queries/insights.sql b/coderd/database/queries/insights.sql index c3dad57d2d673..e7e5f34b807c9 100644 --- a/coderd/database/queries/insights.sql +++ b/coderd/database/queries/insights.sql @@ -24,7 +24,8 @@ ORDER BY user_id ASC; -- GetTemplateInsights has a granularity of 5 minutes where if a session/app was -- in use, we will add 5 minutes to the total usage for that session (per user). WITH d AS ( - SELECT generate_series(@start_time::timestamptz, @end_time::timestamptz, '5 minute'::interval) AS d + -- Subtract 1 second from end_time to avoid including the next interval in the results. + SELECT generate_series(@start_time::timestamptz, (@end_time::timestamptz) - '1 second'::interval, '5 minute'::interval) AS d ), ts AS ( SELECT d::timestamptz AS from_, @@ -71,7 +72,8 @@ FROM usage_by_user; -- interval/template, it will be included in the results with 0 active users. WITH d AS ( -- sqlc workaround, use SELECT generate_series instead of SELECT * FROM generate_series. - SELECT generate_series(@start_time::timestamptz, @end_time::timestamptz, '1 day'::interval) AS d + -- Subtract 1 second from end_time to avoid including the next interval in the results. + SELECT generate_series(@start_time::timestamptz, (@end_time::timestamptz) - '1 second'::interval, '1 day'::interval) AS d ), ts AS ( SELECT d::timestamptz AS from_, @@ -91,15 +93,18 @@ WITH d AS ( ) GROUP BY ts.from_, ts.to_, was.user_id ), template_ids AS ( - SELECT array_agg(DISTINCT template_id) AS ids + SELECT + from_, + array_agg(DISTINCT template_id) AS ids FROM usage_by_day, unnest(template_ids) template_id WHERE template_id IS NOT NULL + GROUP BY from_, template_ids ) SELECT from_ AS start_time, to_ AS end_time, - COALESCE((SELECT ids FROM template_ids), '{}')::uuid[] AS template_ids, + COALESCE((SELECT template_ids.ids FROM template_ids WHERE template_ids.from_ = usage_by_day.from_), '{}')::uuid[] AS template_ids, COUNT(DISTINCT user_id) AS active_users -FROM usage_by_day, unnest(template_ids) as template_id +FROM usage_by_day GROUP BY from_, to_; diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 613a2c07ec428..f108882299b9a 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -168,11 +168,17 @@ func TestUserLatencyInsights(t *testing.T) { defer r.Close() defer w.Close() sess.Stdin = r + sess.Stdout = io.Discard err = sess.Start("cat") require.NoError(t, err) var userLatencies codersdk.UserLatencyInsightsResponse require.Eventuallyf(t, func() bool { + // Keep connection active. + _, err := w.Write([]byte("hello world\n")) + if !assert.NoError(t, err) { + return false + } userLatencies, err = client.UserLatencyInsights(ctx, codersdk.UserLatencyInsightsRequest{ StartTime: today, EndTime: time.Now().UTC().Truncate(time.Hour).Add(time.Hour), // Round up to include the current hour. @@ -182,7 +188,7 @@ func TestUserLatencyInsights(t *testing.T) { return false } return len(userLatencies.Report.Users) > 0 && userLatencies.Report.Users[0].LatencyMS.P50 > 0 - }, testutil.WaitShort, testutil.IntervalFast, "user latency is missing") + }, testutil.WaitMedium, testutil.IntervalFast, "user latency is missing") // We got our latency data, close the connection. _ = sess.Close() @@ -318,8 +324,8 @@ func TestTemplateInsights(t *testing.T) { return false } } - 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") + require.Eventually(t, waitForAppSeconds("reconnecting-pty"), testutil.WaitMedium, testutil.IntervalFast, "reconnecting-pty seconds missing") + require.Eventually(t, waitForAppSeconds("ssh"), testutil.WaitMedium, testutil.IntervalFast, "ssh seconds missing") // We got our data, close down sessions and connections. _ = rpty.Close()