@@ -16,6 +16,7 @@ import (
16
16
17
17
"github.com/google/uuid"
18
18
"github.com/lib/pq"
19
+ "golang.org/x/exp/constraints"
19
20
"golang.org/x/exp/maps"
20
21
"golang.org/x/exp/slices"
21
22
"golang.org/x/xerrors"
@@ -791,6 +792,13 @@ func tagsSubset(m1, m2 map[string]string) bool {
791
792
// default tags when no tag is specified for a provisioner or job
792
793
var tagsUntagged = provisionersdk .MutateTags (uuid .Nil , nil )
793
794
795
+ func least [T constraints.Ordered ](a , b T ) T {
796
+ if a < b {
797
+ return a
798
+ }
799
+ return b
800
+ }
801
+
794
802
func (* FakeQuerier ) AcquireLock (_ context.Context , _ int64 ) error {
795
803
return xerrors .New ("AcquireLock must only be called within a transaction" )
796
804
}
@@ -3237,71 +3245,166 @@ func (q *FakeQuerier) GetTemplateInsights(_ context.Context, arg database.GetTem
3237
3245
return database.GetTemplateInsightsRow {}, err
3238
3246
}
3239
3247
3240
- templateIDSet := make (map [uuid.UUID ]struct {})
3241
- appUsageIntervalsByUser := make (map [uuid.UUID ]map [time.Time ]* database.GetTemplateInsightsRow )
3242
-
3243
3248
q .mutex .RLock ()
3244
3249
defer q .mutex .RUnlock ()
3245
3250
3246
- for _ , s := range q .workspaceAgentStats {
3247
- if s .CreatedAt .Before (arg .StartTime ) || s .CreatedAt .Equal (arg .EndTime ) || s .CreatedAt .After (arg .EndTime ) {
3251
+ /*
3252
+ WITH
3253
+ */
3254
+
3255
+ /*
3256
+ insights AS (
3257
+ SELECT
3258
+ user_id,
3259
+ -- See motivation in GetTemplateInsights for LEAST(SUM(n), 30).
3260
+ LEAST(SUM(usage_mins), 30) AS usage_mins,
3261
+ LEAST(SUM(ssh_mins), 30) AS ssh_mins,
3262
+ LEAST(SUM(sftp_mins), 30) AS sftp_mins,
3263
+ LEAST(SUM(reconnecting_pty_mins), 30) AS reconnecting_pty_mins,
3264
+ LEAST(SUM(vscode_mins), 30) AS vscode_mins,
3265
+ LEAST(SUM(jetbrains_mins), 30) AS jetbrains_mins
3266
+ FROM
3267
+ template_usage_stats
3268
+ WHERE
3269
+ start_time >= @start_time::timestamptz
3270
+ AND end_time <= @end_time::timestamptz
3271
+ AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN template_id = ANY(@template_ids::uuid[]) ELSE TRUE END
3272
+ GROUP BY
3273
+ start_time, user_id
3274
+ ),
3275
+ */
3276
+
3277
+ type insightsGroupBy struct {
3278
+ StartTime time.Time
3279
+ UserID uuid.UUID
3280
+ }
3281
+ type insightsRow struct {
3282
+ insightsGroupBy
3283
+ UsageMins int16
3284
+ SSHMins int16
3285
+ SFTPMins int16
3286
+ ReconnectingPTYMins int16
3287
+ VSCodeMins int16
3288
+ JetBrainsMins int16
3289
+ }
3290
+ insights := make (map [insightsGroupBy ]insightsRow )
3291
+ for _ , stat := range q .templateUsageStats {
3292
+ if stat .StartTime .Before (arg .StartTime ) || stat .EndTime .After (arg .EndTime ) {
3248
3293
continue
3249
3294
}
3250
- if len (arg .TemplateIDs ) > 0 && ! slices .Contains (arg .TemplateIDs , s .TemplateID ) {
3295
+ if len (arg .TemplateIDs ) > 0 && ! slices .Contains (arg .TemplateIDs , stat .TemplateID ) {
3251
3296
continue
3252
3297
}
3253
- if s .ConnectionCount == 0 {
3254
- continue
3298
+ key := insightsGroupBy {
3299
+ StartTime : stat .StartTime ,
3300
+ UserID : stat .UserID ,
3301
+ }
3302
+ row , ok := insights [key ]
3303
+ if ! ok {
3304
+ row = insightsRow {
3305
+ insightsGroupBy : key ,
3306
+ }
3255
3307
}
3308
+ row .UsageMins = least (row .UsageMins + stat .UsageMins , 30 )
3309
+ row .SSHMins = least (row .SSHMins + stat .SshMins , 30 )
3310
+ row .SFTPMins = least (row .SFTPMins + stat .SftpMins , 30 )
3311
+ row .ReconnectingPTYMins = least (row .ReconnectingPTYMins + stat .ReconnectingPtyMins , 30 )
3312
+ row .VSCodeMins = least (row .VSCodeMins + stat .VscodeMins , 30 )
3313
+ row .JetBrainsMins = least (row .JetBrainsMins + stat .JetbrainsMins , 30 )
3314
+ insights [key ] = row
3315
+ }
3256
3316
3257
- templateIDSet [s .TemplateID ] = struct {}{}
3258
- if appUsageIntervalsByUser [s .UserID ] == nil {
3259
- appUsageIntervalsByUser [s .UserID ] = make (map [time.Time ]* database.GetTemplateInsightsRow )
3317
+ /*
3318
+ templates AS (
3319
+ SELECT
3320
+ array_agg(DISTINCT template_id) AS template_ids,
3321
+ array_agg(DISTINCT template_id) FILTER (WHERE ssh_mins > 0) AS ssh_template_ids,
3322
+ array_agg(DISTINCT template_id) FILTER (WHERE sftp_mins > 0) AS sftp_template_ids,
3323
+ array_agg(DISTINCT template_id) FILTER (WHERE reconnecting_pty_mins > 0) AS reconnecting_pty_template_ids,
3324
+ array_agg(DISTINCT template_id) FILTER (WHERE vscode_mins > 0) AS vscode_template_ids,
3325
+ array_agg(DISTINCT template_id) FILTER (WHERE jetbrains_mins > 0) AS jetbrains_template_ids
3326
+ FROM
3327
+ template_usage_stats
3328
+ WHERE
3329
+ start_time >= @start_time::timestamptz
3330
+ AND end_time <= @end_time::timestamptz
3331
+ AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN template_id = ANY(@template_ids::uuid[]) ELSE TRUE END
3332
+ )
3333
+ */
3334
+
3335
+ type templateRow struct {
3336
+ TemplateIDs []uuid.UUID
3337
+ SSHTemplateIDs []uuid.UUID
3338
+ SFTPTemplateIDs []uuid.UUID
3339
+ ReconnectingPTYIDs []uuid.UUID
3340
+ VSCodeTemplateIDs []uuid.UUID
3341
+ JetBrainsTemplateIDs []uuid.UUID
3342
+ }
3343
+ templates := templateRow {}
3344
+ for _ , stat := range q .templateUsageStats {
3345
+ if stat .StartTime .Before (arg .StartTime ) || stat .EndTime .After (arg .EndTime ) {
3346
+ continue
3260
3347
}
3261
- t := s .CreatedAt .Truncate (time .Minute )
3262
- if _ , ok := appUsageIntervalsByUser [s.UserID ][t ]; ! ok {
3263
- appUsageIntervalsByUser [s.UserID ][t ] = & database.GetTemplateInsightsRow {}
3348
+ if len (arg .TemplateIDs ) > 0 && ! slices .Contains (arg .TemplateIDs , stat .TemplateID ) {
3349
+ continue
3264
3350
}
3265
-
3266
- if s . SessionCountJetBrains > 0 {
3267
- appUsageIntervalsByUser [s. UserID ][ t ]. UsageJetbrainsSeconds = 60
3351
+ templates . TemplateIDs = append ( templates . TemplateIDs , stat . TemplateID )
3352
+ if stat . SshMins > 0 {
3353
+ templates . SSHTemplateIDs = append ( templates . SSHTemplateIDs , stat . TemplateID )
3268
3354
}
3269
- if s . SessionCountVSCode > 0 {
3270
- appUsageIntervalsByUser [s. UserID ][ t ]. UsageVscodeSeconds = 60
3355
+ if stat . SftpMins > 0 {
3356
+ templates . SFTPTemplateIDs = append ( templates . SFTPTemplateIDs , stat . TemplateID )
3271
3357
}
3272
- if s . SessionCountReconnectingPTY > 0 {
3273
- appUsageIntervalsByUser [s. UserID ][ t ]. UsageReconnectingPtySeconds = 60
3358
+ if stat . ReconnectingPtyMins > 0 {
3359
+ templates . ReconnectingPTYIDs = append ( templates . ReconnectingPTYIDs , stat . TemplateID )
3274
3360
}
3275
- if s .SessionCountSSH > 0 {
3276
- appUsageIntervalsByUser [s.UserID ][t ].UsageSshSeconds = 60
3361
+ if stat .VscodeMins > 0 {
3362
+ templates .VSCodeTemplateIDs = append (templates .VSCodeTemplateIDs , stat .TemplateID )
3363
+ }
3364
+ if stat .JetbrainsMins > 0 {
3365
+ templates .JetBrainsTemplateIDs = append (templates .JetBrainsTemplateIDs , stat .TemplateID )
3277
3366
}
3278
3367
}
3279
3368
3280
- templateIDs := make ([]uuid.UUID , 0 , len (templateIDSet ))
3281
- for templateID := range templateIDSet {
3282
- templateIDs = append (templateIDs , templateID )
3283
- }
3284
- slices .SortFunc (templateIDs , func (a , b uuid.UUID ) int {
3285
- return slice .Ascending (a .String (), b .String ())
3286
- })
3287
- activeUserIDs := make ([]uuid.UUID , 0 , len (appUsageIntervalsByUser ))
3288
- for userID := range appUsageIntervalsByUser {
3289
- activeUserIDs = append (activeUserIDs , userID )
3290
- }
3369
+ /*
3370
+ SELECT
3371
+ COALESCE((SELECT template_ids FROM templates), '{}')::uuid[] AS template_ids, -- Includes app usage.
3372
+ COALESCE((SELECT ssh_template_ids FROM templates), '{}')::uuid[] AS ssh_template_ids,
3373
+ COALESCE((SELECT sftp_template_ids FROM templates), '{}')::uuid[] AS sftp_template_ids,
3374
+ COALESCE((SELECT reconnecting_pty_template_ids FROM templates), '{}')::uuid[] AS reconnecting_pty_template_ids,
3375
+ COALESCE((SELECT vscode_template_ids FROM templates), '{}')::uuid[] AS vscode_template_ids,
3376
+ COALESCE((SELECT jetbrains_template_ids FROM templates), '{}')::uuid[] AS jetbrains_template_ids,
3377
+ COALESCE(COUNT(DISTINCT user_id), 0)::bigint AS active_users, -- Includes app usage.
3378
+ COALESCE(SUM(usage_mins) * 60, 0)::bigint AS usage_total_seconds, -- Includes app usage.
3379
+ COALESCE(SUM(ssh_mins) * 60, 0)::bigint AS usage_ssh_seconds,
3380
+ COALESCE(SUM(sftp_mins) * 60, 0)::bigint AS usage_sftp_seconds,
3381
+ COALESCE(SUM(reconnecting_pty_mins) * 60, 0)::bigint AS usage_reconnecting_pty_seconds,
3382
+ COALESCE(SUM(vscode_mins) * 60, 0)::bigint AS usage_vscode_seconds,
3383
+ COALESCE(SUM(jetbrains_mins) * 60, 0)::bigint AS usage_jetbrains_seconds
3384
+ FROM
3385
+ insights;
3386
+ */
3291
3387
3292
- result := database.GetTemplateInsightsRow {
3293
- TemplateIDs : templateIDs ,
3294
- ActiveUsers : int64 (len (activeUserIDs )),
3295
- }
3296
- for _ , intervals := range appUsageIntervalsByUser {
3297
- for _ , interval := range intervals {
3298
- result .UsageJetbrainsSeconds += interval .UsageJetbrainsSeconds
3299
- result .UsageVscodeSeconds += interval .UsageVscodeSeconds
3300
- result .UsageReconnectingPtySeconds += interval .UsageReconnectingPtySeconds
3301
- result .UsageSshSeconds += interval .UsageSshSeconds
3302
- }
3303
- }
3304
- return result , nil
3388
+ var row database.GetTemplateInsightsRow
3389
+ row .TemplateIDs = uniqueSortedUUIDs (templates .TemplateIDs )
3390
+ row .SshTemplateIds = uniqueSortedUUIDs (templates .SSHTemplateIDs )
3391
+ row .SftpTemplateIds = uniqueSortedUUIDs (templates .SFTPTemplateIDs )
3392
+ row .ReconnectingPtyTemplateIds = uniqueSortedUUIDs (templates .ReconnectingPTYIDs )
3393
+ row .VscodeTemplateIds = uniqueSortedUUIDs (templates .VSCodeTemplateIDs )
3394
+ row .JetbrainsTemplateIds = uniqueSortedUUIDs (templates .JetBrainsTemplateIDs )
3395
+ activeUserIDs := make (map [uuid.UUID ]struct {})
3396
+ for _ , insight := range insights {
3397
+ activeUserIDs [insight .UserID ] = struct {}{}
3398
+ row .UsageTotalSeconds += int64 (insight .UsageMins ) * 60
3399
+ row .UsageSshSeconds += int64 (insight .SSHMins ) * 60
3400
+ row .UsageSftpSeconds += int64 (insight .SFTPMins ) * 60
3401
+ row .UsageReconnectingPtySeconds += int64 (insight .ReconnectingPTYMins ) * 60
3402
+ row .UsageVscodeSeconds += int64 (insight .VSCodeMins ) * 60
3403
+ row .UsageJetbrainsSeconds += int64 (insight .JetBrainsMins ) * 60
3404
+ }
3405
+ row .ActiveUsers = int64 (len (activeUserIDs ))
3406
+
3407
+ return row , nil
3305
3408
}
3306
3409
3307
3410
func (q * FakeQuerier ) GetTemplateInsightsByInterval (ctx context.Context , arg database.GetTemplateInsightsByIntervalParams ) ([]database.GetTemplateInsightsByIntervalRow , error ) {
0 commit comments