Skip to content

Events Schema

Framedash stores raw telemetry in a ClickHouse events table. You can read it with SQL from three places:

This page lists the columns and the validator rules so you can write queries without the Claude Code plugin.

Raw SQL requires an API key with the data:admin scope (the Full preset). A Read-only key can use the pre-built analytics endpoints and MCP tools but not the raw query path. An Ingest (events:write) key cannot query at all, and cannot enumerate its own project, by design.

Queries are validated, rewritten to enforce project isolation, and run against a read-only connection. The validator enforces:

  • SELECT only. No INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, SYSTEM, OPTIMIZE, KILL, or UNION / EXCEPT / INTERSECT.
  • Only two tables may appear in FROM / JOIN: events (raw events) and daily_sessions_project_mv (pre-aggregated daily sessions). Write plain FROM events; isolation filters are injected for you.
  • Do not reference tenant_id or project_id in your SQL. They are auto-scoped, and referencing them directly is rejected.
  • String literals use single quotes only. Double quotes and backticks are rejected.
  • No SETTINGS, FORMAT, or GLOBAL clauses. IN (SELECT ...) and IN ('a','b') are allowed; IN some_table is not.
  • If you omit a top-level LIMIT, one is appended automatically.

Results are { "rows": [ { "col": "value" } ], "rowCount": 123 }. The CLI and MCP tool return exactly this shape. Calling the REST endpoint directly gets the standard API envelope around it: { "success": true, "data": { "rows": [...], "rowCount": 123 } }. The MCP query tool caps results at 1000 rows; the REST endpoint and CLI cap at 10000. For larger extracts, page with LIMIT ... OFFSET ... or narrow the timestamp range.

ColumnTypeNotes
event_nameStringEvent name, e.g. player_death, level_start, perf_heartbeat
timestampDateTime64(6)Event time (UTC)
session_idStringPlay-session id
player_idStringStable player/device id
position_x, position_y, position_zFloat32World coordinates
map_idStringMap/level identifier
fpsFloat32Frames per second (perf events)
frame_time_msFloat32Frame time in ms (perf events)
memory_used_bytesInt64Memory in bytes (perf events)
gpu_time_msFloat32GPU time in ms (perf events)
game_thread_msFloat32Game-thread time in ms
render_thread_msFloat32Render-thread time in ms
camera_yaw, camera_pitchNullable(Float32)Camera orientation
sourceStringautomated for the SDK’s own internal events (session_start, perf_heartbeat); player for your Track(...) calls. Not a CI indicator — see Conventions below
build_idStringBuild identifier (perf-diff candidate)
platformStringe.g. windows, android
engine_versionStringEngine/build version string
attributesMap(String, String)Custom string attributes
metricsMap(String, Float64)Custom numeric metrics

Map columns use bracket access: attributes['ci.branch'], metrics['score'].

There is no server_timestamp column. Use timestamp for event time. Selecting an unknown column returns a query error, so check the names above first. A rejected query comes back as HTTP 400 with the ClickHouse diagnostic message (unknown column, table, or function; syntax errors; type mismatches; forbidden operations); only infrastructure failures return 500.

  • The automatic performance event is perf_heartbeat. Filter perf queries with WHERE event_name = 'perf_heartbeat'; not every event carries fps / frame_time_ms.
  • source = 'automated' marks the SDK’s own internal events (session_start, perf_heartbeat) as opposed to your Track(...) calls — it is set on every session, CI or not, and does not identify CI runs by itself.
  • To isolate CI profiling data, filter on build_id or the attributes['ci.*'] tags (set by BeginAutomatedSession/BeginAutomatedSessionFromEnvironment), not on source.
  • CI metadata rides in attributes: attributes['ci.branch'], attributes['ci.commit'], attributes['ci.scenario'], alongside the first-class build_id column.
  • Event names are free-form strings you choose in Track(...). The official SDK samples use snake_case (player_death, level_start); keep one convention across your title.
  • Always bound timestamp so queries prune partitions (the table is partitioned by day) and stay fast.

Active players today:

SELECT uniqExact(player_id) AS dau
FROM events
WHERE timestamp >= today()

Event volume by name over the last 7 days:

SELECT event_name, count() AS events, uniqExact(session_id) AS sessions
FROM events
WHERE timestamp >= today() - 7
GROUP BY event_name
ORDER BY events DESC

Performance percentiles per build:

SELECT build_id,
count() AS samples,
round(quantile(0.50)(fps), 1) AS fps_p50,
round(quantile(0.95)(frame_time_ms), 2) AS frametime_p95_ms
FROM events
WHERE event_name = 'perf_heartbeat'
AND timestamp >= today() - 14
GROUP BY build_id
ORDER BY samples DESC