Skip to content

Commit e374af6

Browse files
committed
Fix analytics rollups and enable player telemetry
1 parent 742541b commit e374af6

5 files changed

Lines changed: 136 additions & 36 deletions

File tree

apps/site/app/api/player/telemetry/route.test.mts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,54 @@ test("POST no-ops when telemetry ingest is disabled", async () => {
159159
}
160160
});
161161

162+
test("POST enables telemetry ingest by default in production", async () => {
163+
const env = process.env as Record<string, string | undefined>;
164+
const previousNodeEnv = env.NODE_ENV;
165+
const previousIngest = env.REND_PLAYER_TELEMETRY_INGEST;
166+
const previousPublic = env.NEXT_PUBLIC_REND_PLAYER_TELEMETRY;
167+
const previousToken = env.REND_INTERNAL_TELEMETRY_TOKEN;
168+
env.NODE_ENV = "production";
169+
delete env.REND_PLAYER_TELEMETRY_INGEST;
170+
delete env.NEXT_PUBLIC_REND_PLAYER_TELEMETRY;
171+
delete env.REND_INTERNAL_TELEMETRY_TOKEN;
172+
173+
try {
174+
clearPlayerTelemetryEventsForTests();
175+
const response = await POST(
176+
telemetryRequest({
177+
playback_session_id: "route-session-production-default",
178+
asset_id: "asset-123",
179+
phase: "player_load",
180+
event_time_ms: EVENT_TIME_MS,
181+
})
182+
);
183+
184+
assert.equal(response.status, 200);
185+
assert.deepEqual(await responseJson(response), { status: "ok", accepted: 1 });
186+
} finally {
187+
if (previousNodeEnv === undefined) {
188+
delete env.NODE_ENV;
189+
} else {
190+
env.NODE_ENV = previousNodeEnv;
191+
}
192+
if (previousIngest === undefined) {
193+
delete env.REND_PLAYER_TELEMETRY_INGEST;
194+
} else {
195+
env.REND_PLAYER_TELEMETRY_INGEST = previousIngest;
196+
}
197+
if (previousPublic === undefined) {
198+
delete env.NEXT_PUBLIC_REND_PLAYER_TELEMETRY;
199+
} else {
200+
env.NEXT_PUBLIC_REND_PLAYER_TELEMETRY = previousPublic;
201+
}
202+
if (previousToken === undefined) {
203+
delete env.REND_INTERNAL_TELEMETRY_TOKEN;
204+
} else {
205+
env.REND_INTERNAL_TELEMETRY_TOKEN = previousToken;
206+
}
207+
}
208+
});
209+
162210
test("recent endpoint is production-disabled unless telemetry debug is enabled", async () => {
163211
const env = process.env as Record<string, string | undefined>;
164212
const previousNodeEnv = process.env.NODE_ENV;

apps/site/app/api/player/telemetry/route.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,29 @@ import {
88
export const dynamic = "force-dynamic";
99
export const runtime = "nodejs";
1010

11-
function envBoolean(name: string) {
11+
function envBooleanOverride(name: string) {
1212
const value = (process.env[name] || "").trim().toLowerCase();
13-
return ["1", "true", "yes", "on"].includes(value);
13+
if (!value) return undefined;
14+
if (["1", "true", "yes", "on"].includes(value)) return true;
15+
if (["0", "false", "no", "off"].includes(value)) return false;
16+
return undefined;
1417
}
1518

1619
function envString(name: string, fallback = "") {
1720
return (process.env[name] || fallback).trim();
1821
}
1922

23+
function productionProfile() {
24+
const profile = envString("REND_ENV_PROFILE") || envString("REND_ENV") || process.env.NODE_ENV || "local";
25+
return ["production", "prod"].includes(profile.toLowerCase());
26+
}
27+
2028
function telemetryIngestEnabled() {
21-
return (
22-
envBoolean("REND_PLAYER_TELEMETRY_INGEST") ||
23-
envBoolean("NEXT_PUBLIC_REND_PLAYER_TELEMETRY")
24-
);
29+
const ingestOverride = envBooleanOverride("REND_PLAYER_TELEMETRY_INGEST");
30+
if (ingestOverride !== undefined) return ingestOverride;
31+
const publicOverride = envBooleanOverride("NEXT_PUBLIC_REND_PLAYER_TELEMETRY");
32+
if (publicOverride !== undefined) return publicOverride;
33+
return productionProfile();
2534
}
2635

2736
function controlPlaneUrl(path: string) {
@@ -33,8 +42,7 @@ function telemetryInternalToken() {
3342
const configured =
3443
envString("REND_INTERNAL_TELEMETRY_TOKEN") || envString("REND_EDGE_INTERNAL_TOKEN");
3544
if (configured) return configured;
36-
const profile = envString("REND_ENV_PROFILE") || envString("REND_ENV") || process.env.NODE_ENV || "local";
37-
return ["production", "prod"].includes(profile.toLowerCase()) ? "" : "dev-internal-token";
45+
return productionProfile() ? "" : "dev-internal-token";
3846
}
3947

4048
function jsonResponse(body: unknown, init?: ResponseInit) {

apps/site/app/embed/[assetId]/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,18 @@ function telemetryAppVersion() {
6262
);
6363
}
6464

65+
function telemetryDefaultEnabled() {
66+
const configured = process.env.NEXT_PUBLIC_REND_PLAYER_TELEMETRY?.trim().toLowerCase();
67+
if (configured) return ["1", "true", "yes", "on"].includes(configured);
68+
const profile = (process.env.REND_ENV_PROFILE || process.env.REND_ENV || process.env.NODE_ENV || "local").toLowerCase();
69+
return profile === "production" || profile === "prod";
70+
}
71+
6572
function telemetryEnabled(value: string | string[] | undefined) {
6673
const requested = firstValue(value);
6774
if (requested === "0") return false;
6875
if (requested === "1") return true;
69-
return process.env.NEXT_PUBLIC_REND_PLAYER_TELEMETRY === "1";
76+
return telemetryDefaultEnabled();
7077
}
7178

7279
function playerStartupMode(query: Query): StartupMode {

apps/site/app/watch/[assetId]/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,18 @@ function telemetryAppVersion() {
4646
);
4747
}
4848

49+
function telemetryDefaultEnabled() {
50+
const configured = process.env.NEXT_PUBLIC_REND_PLAYER_TELEMETRY?.trim().toLowerCase();
51+
if (configured) return ["1", "true", "yes", "on"].includes(configured);
52+
const profile = (process.env.REND_ENV_PROFILE || process.env.REND_ENV || process.env.NODE_ENV || "local").toLowerCase();
53+
return profile === "production" || profile === "prod";
54+
}
55+
4956
function telemetryEnabled(value: string | string[] | undefined) {
5057
const requested = Array.isArray(value) ? value[0] : value;
5158
if (requested === "0") return false;
5259
if (requested === "1") return true;
53-
return process.env.NEXT_PUBLIC_REND_PLAYER_TELEMETRY === "1";
60+
return telemetryDefaultEnabled();
5461
}
5562

5663
function playerStartupMode(

services/rend-api/src/telemetry.rs

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,9 +1299,9 @@ fn clickhouse_edge_rollup_refresh_query(window: NormalizedPlaybackAnalyticsWindo
12991299
"\
13001300
INSERT INTO analytics_edge_hourly \
13011301
SELECT \
1302-
organization_id, \
1302+
rollup_organization_id AS organization_id, \
13031303
toStartOfHour(event_observed_at) AS bucket_start, \
1304-
asset_id, \
1304+
rollup_asset_id AS asset_id, \
13051305
count() AS request_count, \
13061306
sum(bytes_served) AS bytes_served, \
13071307
countIf(cache_status = 'HIT') AS cache_hit_count, \
@@ -1312,8 +1312,8 @@ fn clickhouse_edge_rollup_refresh_query(window: NormalizedPlaybackAnalyticsWindo
13121312
FROM ( \
13131313
SELECT \
13141314
event_id, \
1315-
assumeNotNull(any(organization_id)) AS organization_id, \
1316-
any(asset_id) AS asset_id, \
1315+
assumeNotNull(any(organization_id)) AS rollup_organization_id, \
1316+
any(asset_id) AS rollup_asset_id, \
13171317
min(observed_at) AS event_observed_at, \
13181318
any(bytes_served) AS bytes_served, \
13191319
any(cache_status) AS cache_status, \
@@ -1326,7 +1326,7 @@ fn clickhouse_edge_rollup_refresh_query(window: NormalizedPlaybackAnalyticsWindo
13261326
AND observed_at < fromUnixTimestamp64Milli({}) \
13271327
GROUP BY event_id \
13281328
) \
1329-
GROUP BY organization_id, bucket_start, asset_id",
1329+
GROUP BY rollup_organization_id, bucket_start, rollup_asset_id",
13301330
window.started_at.timestamp_millis(),
13311331
window.ended_at.timestamp_millis(),
13321332
)
@@ -1337,40 +1337,40 @@ fn clickhouse_player_rollup_refresh_query(window: NormalizedPlaybackAnalyticsWin
13371337
"\
13381338
INSERT INTO analytics_player_hourly \
13391339
SELECT \
1340-
organization_id, \
1340+
rollup_organization_id AS organization_id, \
13411341
bucket_start, \
1342-
asset_id, \
1342+
rollup_asset_id AS asset_id, \
13431343
count() AS sessions, \
13441344
countIf(reached_first_frame) AS views, \
13451345
countIf(startup_failed) AS startup_failures, \
1346-
sum(watch_time_ms) AS watch_time_ms, \
1347-
countIf(stall_duration_ms > 0) AS stalled_sessions, \
1348-
sum(stall_count) AS stall_count, \
1349-
sum(stall_duration_ms) AS stall_duration_ms, \
1350-
sum(playback_failures) AS playback_failures, \
1346+
sum(session_watch_time_ms) AS watch_time_ms, \
1347+
countIf(session_stall_duration_ms > 0) AS stalled_sessions, \
1348+
sum(session_stall_count) AS stall_count, \
1349+
sum(session_stall_duration_ms) AS stall_duration_ms, \
1350+
sum(session_playback_failures) AS playback_failures, \
13511351
quantileTDigestIf(0.5)(first_frame_ms, first_frame_ms > 0) AS first_frame_p50_ms, \
13521352
quantileTDigestIf(0.95)(first_frame_ms, first_frame_ms > 0) AS first_frame_p95_ms, \
13531353
now64(3) AS updated_at \
13541354
FROM ( \
13551355
SELECT \
1356-
organization_id, \
1357-
asset_id, \
1358-
playback_session_id, \
1359-
toStartOfHour(min(observed_at)) AS bucket_start, \
1356+
rollup_organization_id, \
1357+
rollup_asset_id, \
1358+
rollup_playback_session_id, \
1359+
toStartOfHour(min(event_observed_at)) AS bucket_start, \
13601360
countIf(phase = 'first_frame') > 0 AS reached_first_frame, \
13611361
countIf(phase = 'bootstrap_failure') > 0 AS startup_failed, \
13621362
minIf(first_frame_ms, phase = 'first_frame' AND first_frame_ms > 0) AS first_frame_ms, \
1363-
sumIf(watch_delta_ms, phase = 'watch_heartbeat') AS watch_time_ms, \
1364-
countIf(phase = 'stall_end') AS stall_count, \
1365-
sumIf(stall_duration_ms, phase = 'stall_end') AS stall_duration_ms, \
1366-
countIf(phase = 'playback_failure') AS playback_failures \
1363+
sumIf(watch_delta_ms, phase = 'watch_heartbeat') AS session_watch_time_ms, \
1364+
countIf(phase = 'stall_end') AS session_stall_count, \
1365+
sumIf(stall_duration_ms, phase = 'stall_end') AS session_stall_duration_ms, \
1366+
countIf(phase = 'playback_failure') AS session_playback_failures \
13671367
FROM ( \
13681368
SELECT \
13691369
event_id, \
1370-
any(organization_id) AS organization_id, \
1371-
any(asset_id) AS asset_id, \
1372-
any(playback_session_id) AS playback_session_id, \
1373-
min(observed_at) AS observed_at, \
1370+
any(organization_id) AS rollup_organization_id, \
1371+
any(asset_id) AS rollup_asset_id, \
1372+
any(playback_session_id) AS rollup_playback_session_id, \
1373+
min(observed_at) AS event_observed_at, \
13741374
any(phase) AS phase, \
13751375
any(first_frame_ms) AS first_frame_ms, \
13761376
any(stall_duration_ms) AS stall_duration_ms, \
@@ -1380,9 +1380,9 @@ fn clickhouse_player_rollup_refresh_query(window: NormalizedPlaybackAnalyticsWin
13801380
AND observed_at < fromUnixTimestamp64Milli({}) \
13811381
GROUP BY event_id \
13821382
) \
1383-
GROUP BY organization_id, asset_id, playback_session_id \
1383+
GROUP BY rollup_organization_id, rollup_asset_id, rollup_playback_session_id \
13841384
) \
1385-
GROUP BY organization_id, bucket_start, asset_id",
1385+
GROUP BY rollup_organization_id, bucket_start, rollup_asset_id",
13861386
window.started_at.timestamp_millis(),
13871387
window.ended_at.timestamp_millis(),
13881388
)
@@ -1927,6 +1927,36 @@ mod tests {
19271927
);
19281928
}
19291929

1930+
#[test]
1931+
fn rollup_queries_do_not_reuse_source_column_names_for_aggregates() {
1932+
let window = NormalizedPlaybackAnalyticsWindow {
1933+
started_at: DateTime::parse_from_rfc3339("2026-06-13T11:00:00.000Z")
1934+
.unwrap()
1935+
.with_timezone(&Utc),
1936+
ended_at: DateTime::parse_from_rfc3339("2026-06-13T12:00:00.000Z")
1937+
.unwrap()
1938+
.with_timezone(&Utc),
1939+
};
1940+
1941+
let edge_query = clickhouse_edge_rollup_refresh_query(window);
1942+
assert!(edge_query.contains("rollup_organization_id AS organization_id"));
1943+
assert!(edge_query.contains("rollup_asset_id AS asset_id"));
1944+
assert!(
1945+
edge_query.contains("GROUP BY rollup_organization_id, bucket_start, rollup_asset_id")
1946+
);
1947+
1948+
let player_query = clickhouse_player_rollup_refresh_query(window);
1949+
assert!(player_query.contains("rollup_organization_id AS organization_id"));
1950+
assert!(player_query.contains("rollup_asset_id AS asset_id"));
1951+
assert!(player_query.contains("min(observed_at) AS event_observed_at"));
1952+
assert!(player_query.contains("sum(session_watch_time_ms) AS watch_time_ms"));
1953+
assert!(player_query.contains("countIf(session_stall_duration_ms > 0)"));
1954+
assert!(player_query.contains("sum(session_playback_failures) AS playback_failures"));
1955+
assert!(player_query.contains(
1956+
"GROUP BY rollup_organization_id, rollup_asset_id, rollup_playback_session_id"
1957+
));
1958+
}
1959+
19301960
#[test]
19311961
fn clickhouse_rows_use_artifact_billing_metadata_for_delivery() {
19321962
let ingested_at = DateTime::parse_from_rfc3339("2026-06-13T12:00:01.000Z")

0 commit comments

Comments
 (0)