diff --git a/generated/schema.graphql b/generated/schema.graphql index 80afcf1a..3c032f3a 100644 --- a/generated/schema.graphql +++ b/generated/schema.graphql @@ -788,6 +788,7 @@ type TelemetryActivityPoint { type TelemetryFeatureAdoption { enabled: Int! + flagged: Int! installsUsing: Int! key: String! reporting: Int! @@ -797,6 +798,7 @@ type TelemetryFeatureAdoption { type TelemetryFleetTotals { dedicatedServers: Int! gameServerNodes: Int! + gpuNodes: Int! mapsPlayed: Int! matches: Int! matchesImported: Int! diff --git a/generated/schema.ts b/generated/schema.ts index f97f3efb..b1db62b8 100644 --- a/generated/schema.ts +++ b/generated/schema.ts @@ -711,6 +711,7 @@ export interface TelemetryActivityPoint { export interface TelemetryFeatureAdoption { enabled: Scalars['Int'] + flagged: Scalars['Int'] installsUsing: Scalars['Int'] key: Scalars['String'] reporting: Scalars['Int'] @@ -721,6 +722,7 @@ export interface TelemetryFeatureAdoption { export interface TelemetryFleetTotals { dedicatedServers: Scalars['Int'] gameServerNodes: Scalars['Int'] + gpuNodes: Scalars['Int'] mapsPlayed: Scalars['Int'] matches: Scalars['Int'] matchesImported: Scalars['Int'] @@ -36349,6 +36351,7 @@ export interface TelemetryActivityPointGenqlSelection{ export interface TelemetryFeatureAdoptionGenqlSelection{ enabled?: boolean | number + flagged?: boolean | number installsUsing?: boolean | number key?: boolean | number reporting?: boolean | number @@ -36360,6 +36363,7 @@ export interface TelemetryFeatureAdoptionGenqlSelection{ export interface TelemetryFleetTotalsGenqlSelection{ dedicatedServers?: boolean | number gameServerNodes?: boolean | number + gpuNodes?: boolean | number mapsPlayed?: boolean | number matches?: boolean | number matchesImported?: boolean | number diff --git a/generated/types.ts b/generated/types.ts index 0c0e5328..eee42056 100644 --- a/generated/types.ts +++ b/generated/types.ts @@ -2511,6 +2511,9 @@ export default { "enabled": [ 40 ], + "flagged": [ + 40 + ], "installsUsing": [ 40 ], @@ -2534,6 +2537,9 @@ export default { "gameServerNodes": [ 40 ], + "gpuNodes": [ + 40 + ], "mapsPlayed": [ 40 ], diff --git a/hasura/metadata/actions.graphql b/hasura/metadata/actions.graphql index 743b83a7..a9df445b 100644 --- a/hasura/metadata/actions.graphql +++ b/hasura/metadata/actions.graphql @@ -1181,6 +1181,7 @@ type TelemetryInstallCounts { type TelemetryFleetTotals { gameServerNodes: Int! + gpuNodes: Int! servers: Int! dedicatedServers: Int! publicServers: Int! diff --git a/src/telemetry/telemetry.service.ts b/src/telemetry/telemetry.service.ts index 5362406c..747398d0 100644 --- a/src/telemetry/telemetry.service.ts +++ b/src/telemetry/telemetry.service.ts @@ -29,6 +29,14 @@ export class TelemetryService { string, { setting: string; defaultEnabled: boolean } > = { + // Not every switch is a `public.` setting — auto highlight generation is + // an unprefixed one, and the GPU workloads below are toggled per node. + highlights: { setting: "auto_generate_match_clips", defaultEnabled: false }, + highlights_imported: { + setting: "auto_generate_match_clips_imported", + defaultEnabled: false, + }, + clip_branding: { setting: "clip_bake_branding", defaultEnabled: false }, leagues: { setting: "public.leagues_enabled", defaultEnabled: false }, seasons: { setting: "public.seasons_enabled", defaultEnabled: false }, events: { setting: "public.events_enabled", defaultEnabled: false }, @@ -164,6 +172,7 @@ export class TelemetryService { enabled: counts.nodes_enabled, online: counts.nodes_online, regions: counts.nodes_regions, + gpu: counts.gpu_nodes, }, servers: { total: counts.servers_total, @@ -302,6 +311,7 @@ export class TelemetryService { const [row] = await this.postgres.query>>( `SELECT coalesce(sum((payload->'nodes'->>'total')::numeric), 0) AS "gameServerNodes", + coalesce(sum((payload->'nodes'->>'gpu')::numeric), 0) AS "gpuNodes", coalesce(sum((payload->'servers'->>'total')::numeric), 0) AS servers, coalesce(sum((payload->'servers'->>'dedicated')::numeric), 0) AS "dedicatedServers", coalesce(sum((payload->'servers'->>'public')::numeric), 0) AS "publicServers", @@ -323,6 +333,7 @@ export class TelemetryService { return TelemetryService.toIntegers(row, [ "gameServerNodes", + "gpuNodes", "servers", "dedicatedServers", "publicServers", @@ -517,6 +528,7 @@ export class TelemetryService { enabled: int(nodes.enabled), online: int(nodes.online), regions: int(nodes.regions), + gpu: int(nodes.gpu), }, servers: { total: int(servers.total), @@ -639,7 +651,7 @@ export class TelemetryService { settings: Map, counts: TelemetryCounts, ): Record { - const usage: Record = { + const usage: Record = { tournaments: counts.tournaments, leagues: counts.league_seasons, seasons: counts.seasons, @@ -659,6 +671,16 @@ export class TelemetryService { sanctions: counts.sanctions, api_keys: counts.api_keys, gamedata_validations: counts.gamedata_validations, + demo_playback: counts.demos, + live_streaming: null, + }; + + // Switched per GPU node instead of by a setting: on means at least one node + // is currently carrying that workload. + const gpuWorkloads: Record = { + demo_playback: counts.gpu_demo_nodes > 0, + clip_renders: counts.gpu_render_nodes > 0, + live_streaming: counts.gpu_stream_nodes > 0, }; const features: Record = {}; @@ -666,6 +688,7 @@ export class TelemetryService { for (const key of new Set([ ...Object.keys(TelemetryService.FeatureFlags), ...Object.keys(usage), + ...Object.keys(gpuWorkloads), ])) { const flag = TelemetryService.FeatureFlags[key]; @@ -675,7 +698,7 @@ export class TelemetryService { settings.get(flag.setting), flag.defaultEnabled, ) - : null, + : (gpuWorkloads[key] ?? null), count: usage[key] ?? null, }; } @@ -801,6 +824,17 @@ export class TelemetryService { (SELECT count(DISTINCT region) FROM public.game_server_nodes WHERE region IS NOT NULL) AS nodes_regions, + -- Demo playback, clip rendering and live streaming are each switched + -- per GPU node rather than by a setting, so "on" means at least one + -- node is carrying that workload. + (SELECT count(*) FROM public.game_server_nodes WHERE gpu) AS gpu_nodes, + (SELECT count(*) FROM public.game_server_nodes + WHERE gpu AND gpu_demos_enabled) AS gpu_demo_nodes, + (SELECT count(*) FROM public.game_server_nodes + WHERE gpu AND gpu_rendering_enabled) AS gpu_render_nodes, + (SELECT count(*) FROM public.game_server_nodes + WHERE gpu AND gpu_streaming_enabled) AS gpu_stream_nodes, + (SELECT count(*) FROM public.servers) AS servers_total, (SELECT count(*) FROM public.servers WHERE enabled) AS servers_enabled, (SELECT count(*) FROM public.servers WHERE is_dedicated) AS servers_dedicated, @@ -854,14 +888,14 @@ export class TelemetryService { JOIN public.matches m ON m.lineup_1_id = p.match_lineup_id OR m.lineup_2_id = p.match_lineup_id WHERE p.steam_id IS NOT NULL - AND m.started_at IS NOT NULL + AND ${TelemetryService.nativeMatch("m")} AND m.effective_at >= now() - interval '7 days') AS players_active_7d, (SELECT count(DISTINCT p.steam_id) FROM public.match_lineup_players p JOIN public.matches m ON m.lineup_1_id = p.match_lineup_id OR m.lineup_2_id = p.match_lineup_id WHERE p.steam_id IS NOT NULL - AND m.started_at IS NOT NULL + AND ${TelemetryService.nativeMatch("m")} AND m.effective_at >= now() - interval '30 days') AS players_active_30d, (SELECT count(*) FROM public.tournaments) AS tournaments, diff --git a/src/telemetry/types/TelemetryPayload.ts b/src/telemetry/types/TelemetryPayload.ts index 0bc91ec7..7e1350f6 100644 --- a/src/telemetry/types/TelemetryPayload.ts +++ b/src/telemetry/types/TelemetryPayload.ts @@ -16,6 +16,7 @@ export type TelemetryPayload = { enabled: number; online: number; regions: number; + gpu: number; }; servers: { total: number; diff --git a/test/telemetry.spec.ts b/test/telemetry.spec.ts index db7d0ff6..2a2e2028 100644 --- a/test/telemetry.spec.ts +++ b/test/telemetry.spec.ts @@ -137,6 +137,11 @@ describe("telemetry (SQL-driven)", () => { expect(payload.features.events.enabled).toBe(false); expect(payload.features.scrims.enabled).toBe(true); expect(payload.features.highlights.count).toBe(0); + // Auto highlights is gated by an unprefixed `auto_generate_match_clips`. + expect(payload.features.highlights.enabled).toBe(false); + // GPU workloads are switched per node, not by a setting. + expect(payload.features.demo_playback.enabled).toBe(false); + expect(payload.features.clip_renders.enabled).toBe(false); }); it("keeps the install id out of the guest-readable settings namespace", async () => { @@ -164,7 +169,7 @@ describe("telemetry (SQL-driven)", () => { installed_at: "2024-01-01T00:00:00.000Z", panel_version: "deadbeef", plugin_runtime: "swiftly", - nodes: { total: 2, enabled: 2, online: 1, regions: 1 }, + nodes: { total: 2, enabled: 2, online: 1, regions: 1, gpu: 1 }, servers: { total: 10, enabled: 9, @@ -304,7 +309,7 @@ describe("telemetry (SQL-driven)", () => { installed_at: "2024-01-01T00:00:00.000Z", panel_version: "cafebabe", plugin_runtime: "css", - nodes: { total: 1, enabled: 1, online: 1, regions: 1 }, + nodes: { total: 1, enabled: 1, online: 1, regions: 1, gpu: 1 }, servers: { total: servers, enabled: servers,