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