@@ -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_agent AS (
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 w ON (
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 wa ON (
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_insights AS (
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
+ WHEN was .access_method = ' terminal'
313
+ THEN ' [terminal]' -- Unique name, app names can't contain brackets.
314
+ ELSE was .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 (DISTINCT s .minute_bucket ) AS app_minutes
319
+ FROM
320
+ workspace_app_stats AS was
321
+ JOIN
322
+ workspaces AS 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_name AS slug_or_port,
351
+ display_name AS 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
0 commit comments