Skip to content

Commit 424298e

Browse files
committed
feat(coderd/database): rewrite GetTemplateAppInsights to use template_usage_stats
1 parent bf27a9f commit 424298e

File tree

4 files changed

+186
-114
lines changed

4 files changed

+186
-114
lines changed

coderd/database/dbmem/dbmem.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3000,8 +3000,8 @@ func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.G
30003000
ActiveUserIDs: activeUserIDs,
30013001
AccessMethod: appKey.AccessMethod,
30023002
SlugOrPort: appKey.SlugOrPort,
3003-
DisplayName: sql.NullString{String: appKey.DisplayName, Valid: appKey.DisplayName != ""},
3004-
Icon: sql.NullString{String: appKey.Icon, Valid: appKey.Icon != ""},
3003+
DisplayName: appKey.DisplayName,
3004+
Icon: appKey.Icon,
30053005
IsApp: appKey.Slug != "",
30063006
UsageSeconds: usage,
30073007
})

coderd/database/queries.sql.go

Lines changed: 94 additions & 58 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/insights.sql

Lines changed: 84 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -193,57 +193,93 @@ GROUP BY template_id;
193193
-- GetTemplateAppInsights returns the aggregate usage of each app in a given
194194
-- timeframe. The result can be filtered on template_ids, meaning only user data
195195
-- from workspaces based on those templates will be included.
196-
WITH app_stats_by_user_and_agent AS (
197-
SELECT
198-
s.start_time,
199-
60 as seconds,
200-
w.template_id,
201-
was.user_id,
202-
was.agent_id,
203-
was.access_method,
204-
was.slug_or_port,
205-
wa.display_name,
206-
wa.icon,
207-
(wa.slug IS NOT NULL)::boolean AS is_app
208-
FROM workspace_app_stats was
209-
JOIN workspaces w ON (
210-
w.id = was.workspace_id
211-
AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN w.template_id = ANY(@template_ids::uuid[]) ELSE TRUE END
212-
)
213-
-- We do a left join here because we want to include user IDs that have used
214-
-- e.g. ports when counting active users.
215-
LEFT JOIN workspace_apps wa ON (
216-
wa.agent_id = was.agent_id
217-
AND wa.slug = was.slug_or_port
196+
WITH
197+
app_insights AS (
198+
SELECT
199+
tus.user_id,
200+
array_agg(DISTINCT tus.template_id)::uuid[] AS template_ids,
201+
app_usage.key::text AS app_name,
202+
COALESCE(wa.display_name, '') AS display_name,
203+
COALESCE(wa.icon, '') AS icon,
204+
(wa.slug IS NOT NULL)::boolean AS is_app,
205+
LEAST(SUM(app_usage.value::int), 30) AS app_usage_mins
206+
FROM
207+
template_usage_stats AS tus, jsonb_each(app_usage_mins) AS app_usage
208+
LEFT JOIN LATERAL (
209+
-- Fetch the latest app info for each app based on slug and template.
210+
SELECT
211+
app.display_name,
212+
app.icon,
213+
app.slug
214+
FROM
215+
workspace_apps AS app
216+
JOIN
217+
workspace_agents AS agent
218+
ON
219+
agent.id = app.agent_id
220+
JOIN
221+
workspace_resources AS resource
222+
ON
223+
resource.id = agent.resource_id
224+
JOIN
225+
workspace_builds AS build
226+
ON
227+
build.job_id = resource.job_id
228+
JOIN
229+
workspaces AS workspace
230+
ON
231+
workspace.id = build.workspace_id
232+
WHERE
233+
-- Requires lateral join.
234+
app.slug = app_usage.key
235+
AND workspace.owner_id = tus.user_id
236+
AND workspace.template_id = tus.template_id
237+
ORDER BY
238+
app.created_at DESC
239+
LIMIT 1
240+
) AS wa
241+
ON
242+
true
243+
WHERE
244+
tus.start_time >= @start_time::timestamptz
245+
AND tus.end_time <= @end_time::timestamptz
246+
AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN tus.template_id = ANY(@template_ids::uuid[]) ELSE TRUE END
247+
GROUP BY
248+
tus.start_time, tus.user_id, app_usage.key::text, wa.display_name, wa.icon, wa.slug
249+
),
250+
templates AS (
251+
SELECT
252+
app_name,
253+
display_name,
254+
icon,
255+
is_app,
256+
array_agg(DISTINCT template_id)::uuid[] AS template_ids
257+
FROM
258+
app_insights, unnest(template_ids) AS template_id
259+
GROUP BY
260+
app_name, display_name, icon, is_app
218261
)
219-
-- This table contains both 1 minute entries and >1 minute entries,
220-
-- to calculate this with our uniqueness constraints, we generate series
221-
-- for the longer intervals.
222-
CROSS JOIN LATERAL generate_series(
223-
date_trunc('minute', was.session_started_at),
224-
-- Subtract 1 microsecond to avoid creating an extra series.
225-
date_trunc('minute', was.session_ended_at - '1 microsecond'::interval),
226-
'1 minute'::interval
227-
) s(start_time)
228-
WHERE
229-
s.start_time >= @start_time::timestamptz
230-
-- Subtract one minute because the series only contains the start time.
231-
AND s.start_time < (@end_time::timestamptz) - '1 minute'::interval
232-
GROUP BY s.start_time, w.template_id, was.user_id, was.agent_id, was.access_method, was.slug_or_port, wa.display_name, wa.icon, wa.slug
233-
)
234262

235263
SELECT
236-
array_agg(DISTINCT template_id)::uuid[] AS template_ids,
237-
-- Return IDs so we can combine this with GetTemplateInsights.
238-
array_agg(DISTINCT user_id)::uuid[] AS active_user_ids,
239-
access_method,
240-
slug_or_port,
241-
display_name,
242-
icon,
243-
is_app,
244-
SUM(seconds) AS usage_seconds
245-
FROM app_stats_by_user_and_agent
246-
GROUP BY access_method, slug_or_port, display_name, icon, is_app;
264+
t.template_ids,
265+
array_agg(DISTINCT ai.user_id)::uuid[] AS active_user_ids,
266+
''::text AS access_method, -- TODO(mafredri): Remove.
267+
ai.app_name AS slug_or_port,
268+
ai.display_name,
269+
ai.icon,
270+
ai.is_app,
271+
(SUM(ai.app_usage_mins) * 60)::bigint AS usage_seconds
272+
FROM
273+
app_insights AS ai
274+
JOIN
275+
templates AS t
276+
ON
277+
ai.app_name = t.app_name
278+
AND ai.display_name = t.display_name
279+
AND ai.icon = t.icon
280+
AND ai.is_app = t.is_app
281+
GROUP BY
282+
t.template_ids, ai.app_name, ai.display_name, ai.icon, ai.is_app;
247283

248284
-- name: GetTemplateAppInsightsByTemplate :many
249285
WITH app_stats_by_user_and_agent AS (

coderd/insights.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -474,11 +474,11 @@ func convertTemplateInsightsApps(usage database.GetTemplateInsightsRow, appUsage
474474
if a.SlugOrPort != b.SlugOrPort {
475475
return strings.Compare(a.SlugOrPort, b.SlugOrPort)
476476
}
477-
if a.DisplayName.String != b.DisplayName.String {
478-
return strings.Compare(a.DisplayName.String, b.DisplayName.String)
477+
if a.DisplayName != b.DisplayName {
478+
return strings.Compare(a.DisplayName, b.DisplayName)
479479
}
480-
if a.Icon.String != b.Icon.String {
481-
return strings.Compare(a.Icon.String, b.Icon.String)
480+
if a.Icon != b.Icon {
481+
return strings.Compare(a.Icon, b.Icon)
482482
}
483483
if !a.IsApp && b.IsApp {
484484
return -1
@@ -496,9 +496,9 @@ func convertTemplateInsightsApps(usage database.GetTemplateInsightsRow, appUsage
496496
apps = append(apps, codersdk.TemplateAppUsage{
497497
TemplateIDs: app.TemplateIDs,
498498
Type: codersdk.TemplateAppsTypeApp,
499-
DisplayName: app.DisplayName.String,
499+
DisplayName: app.DisplayName,
500500
Slug: app.SlugOrPort,
501-
Icon: app.Icon.String,
501+
Icon: app.Icon,
502502
Seconds: app.UsageSeconds,
503503
})
504504
}

0 commit comments

Comments
 (0)