@@ -293,51 +293,70 @@ GROUP BY
293
293
t .template_ids ,ai .app_name ,ai .display_name ,ai .icon ,ai .is_app ;
294
294
295
295
-- name: GetTemplateAppInsightsByTemplate :many
296
- WITH app_stats_by_user_and_agentAS (
297
- SELECT
298
- s .start_time ,
299
- 60 as seconds,
300
- w .template_id ,
301
- was .user_id ,
302
- was .agent_id ,
303
- was .slug_or_port ,
304
- wa .display_name ,
305
- (wa .slug IS NOT NULL )::boolean AS is_app
306
- FROM workspace_app_stats was
307
- JOIN workspaces wON (
308
- w .id = was .workspace_id
309
- )
310
- -- We do a left join here because we want to include user IDs that have used
311
- -- e.g. ports when counting active users.
312
- LEFT JOIN workspace_apps waON (
313
- wa .agent_id = was .agent_id
314
- AND wa .slug = was .slug_or_port
296
+ -- GetTemplateAppInsightsByTemplate is used for Prometheus metrics. Keep
297
+ -- in sync with GetTemplateAppInsights and UpsertTemplateUsageStats.
298
+ WITH
299
+ -- This CTE is used to explode app usage into minute buckets, then
300
+ -- flatten the users app usage within the template so that usage in
301
+ -- multiple workspaces under one template is only counted once for
302
+ -- every minute.
303
+ app_insightsAS (
304
+ SELECT
305
+ w .template_id ,
306
+ was .user_id ,
307
+ -- Both app stats and agent stats track web terminal usage, but
308
+ -- by different means. The app stats value should be more
309
+ -- accurate so we don't want to discard it just yet.
310
+ CASE
311
+ WHENwas .access_method = ' terminal'
312
+ THEN' [terminal]' -- Unique name, app names can't contain brackets.
313
+ ELSEwas .slug_or_port
314
+ END::text AS app_name,
315
+ COALESCE(wa .display_name ,' ' )AS display_name,
316
+ (wa .slug IS NOT NULL )::boolean AS is_app,
317
+ COUNT (DISTINCTs .minute_bucket )AS app_minutes
318
+ FROM
319
+ workspace_app_statsAS was
320
+ JOIN
321
+ workspacesAS w
322
+ ON
323
+ w .id = was .workspace_id
324
+ -- We do a left join here because we want to include user IDs that have used
325
+ -- e.g. ports when counting active users.
326
+ LEFT JOIN
327
+ workspace_apps wa
328
+ ON
329
+ wa .agent_id = was .agent_id
330
+ AND wa .slug = was .slug_or_port
331
+ -- Generate a series of minute buckets for each session for computing the
332
+ -- mintes/bucket.
333
+ CROSS JOIN
334
+ generate_series(
335
+ date_trunc(' minute' ,was .session_started_at ),
336
+ -- Subtract 1 microsecond to avoid creating an extra series.
337
+ date_trunc(' minute' ,was .session_ended_at - ' 1 microsecond' ::interval),
338
+ ' 1 minute' ::interval
339
+ )AS s(minute_bucket)
340
+ WHERE
341
+ s .minute_bucket >= @start_time::timestamptz
342
+ AND s .minute_bucket < @end_time::timestamptz
343
+ GROUP BY
344
+ w .template_id ,was .user_id ,was .access_method ,was .slug_or_port ,wa .display_name ,wa .slug
315
345
)
316
- -- This table contains both 1 minute entries and >1 minute entries,
317
- -- to calculate this with our uniqueness constraints, we generate series
318
- -- for the longer intervals.
319
- CROSS JOIN LATERAL generate_series(
320
- date_trunc(' minute' ,was .session_started_at ),
321
- -- Subtract 1 microsecond to avoid creating an extra series.
322
- date_trunc(' minute' ,was .session_ended_at - ' 1 microsecond' ::interval),
323
- ' 1 minute' ::interval
324
- ) s(start_time)
325
- WHERE
326
- s .start_time >= @start_time::timestamptz
327
- -- Subtract one minute because the series only contains the start time.
328
- AND s .start_time < (@end_time::timestamptz )- ' 1 minute' ::interval
329
- GROUP BY s .start_time ,w .template_id ,was .user_id ,was .agent_id ,was .slug_or_port ,wa .display_name ,wa .slug
330
- )
331
346
332
347
SELECT
333
348
template_id,
334
- display_name,
335
- slug_or_port,
336
- COALESCE(COUNT (DISTINCT user_id))::bigint AS active_users,
337
- SUM (seconds)AS usage_seconds
338
- FROM app_stats_by_user_and_agent
339
- WHERE is_app IS TRUE
340
- GROUP BY template_id, display_name, slug_or_port;
349
+ app_nameAS slug_or_port,
350
+ display_nameAS display_name,
351
+ COUNT (DISTINCT user_id)::bigint AS active_users,
352
+ (SUM (app_minutes)* 60 )::bigint AS usage_seconds
353
+ FROM
354
+ app_insights
355
+ WHERE
356
+ is_app IS TRUE
357
+ GROUP BY
358
+ template_id, slug_or_port, display_name;
359
+
341
360
342
361
-- name: GetTemplateInsightsByInterval :many
343
362
-- GetTemplateInsightsByInterval returns all intervals between start and end