Events Schema
Framedash stores raw telemetry in a ClickHouse events table. You can read it with SQL from three places:
- The REST
POST /v1/queryendpoint. - The
framedash queryCLI command. - The MCP
querytool.
This page lists the columns and the validator rules so you can write queries without the Claude Code plugin.
Access and scopes
Section titled “Access and scopes”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.
Query rules
Section titled “Query rules”Queries are validated, rewritten to enforce project isolation, and run against a read-only connection. The validator enforces:
SELECTonly. NoINSERT,UPDATE,DELETE,DROP,ALTER,CREATE,TRUNCATE,SYSTEM,OPTIMIZE,KILL, orUNION/EXCEPT/INTERSECT.- Only two tables may appear in
FROM/JOIN:events(raw events) anddaily_sessions_project_mv(pre-aggregated daily sessions). Write plainFROM events; isolation filters are injected for you. - Do not reference
tenant_idorproject_idin 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, orGLOBALclauses.IN (SELECT ...)andIN ('a','b')are allowed;IN some_tableis 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.
Columns
Section titled “Columns”| Column | Type | Notes |
|---|---|---|
event_name | String | Event name, e.g. player_death, level_start, perf_heartbeat |
timestamp | DateTime64(6) | Event time (UTC) |
session_id | String | Play-session id |
player_id | String | Stable player/device id |
position_x, position_y, position_z | Float32 | World coordinates |
map_id | String | Map/level identifier |
fps | Float32 | Frames per second (perf events) |
frame_time_ms | Float32 | Frame time in ms (perf events) |
memory_used_bytes | Int64 | Memory in bytes (perf events) |
gpu_time_ms | Float32 | GPU time in ms (perf events) |
game_thread_ms | Float32 | Game-thread time in ms |
render_thread_ms | Float32 | Render-thread time in ms |
camera_yaw, camera_pitch | Nullable(Float32) | Camera orientation |
source | String | automated for the SDK’s own internal events (session_start, perf_heartbeat); player for your Track(...) calls. Not a CI indicator — see Conventions below |
build_id | String | Build identifier (perf-diff candidate) |
platform | String | e.g. windows, android |
engine_version | String | Engine/build version string |
attributes | Map(String, String) | Custom string attributes |
metrics | Map(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.
Conventions
Section titled “Conventions”- The automatic performance event is
perf_heartbeat. Filter perf queries withWHERE event_name = 'perf_heartbeat'; not every event carriesfps/frame_time_ms. source = 'automated'marks the SDK’s own internal events (session_start,perf_heartbeat) as opposed to yourTrack(...)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_idor theattributes['ci.*']tags (set byBeginAutomatedSession/BeginAutomatedSessionFromEnvironment), not onsource. - CI metadata rides in
attributes:attributes['ci.branch'],attributes['ci.commit'],attributes['ci.scenario'], alongside the first-classbuild_idcolumn. - Event names are free-form strings you choose in
Track(...). The official SDK samples usesnake_case(player_death,level_start); keep one convention across your title. - Always bound
timestampso queries prune partitions (the table is partitioned by day) and stay fast.
Examples
Section titled “Examples”Active players today:
SELECT uniqExact(player_id) AS dauFROM eventsWHERE timestamp >= today()Event volume by name over the last 7 days:
SELECT event_name, count() AS events, uniqExact(session_id) AS sessionsFROM eventsWHERE timestamp >= today() - 7GROUP BY event_nameORDER BY events DESCPerformance 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_msFROM eventsWHERE event_name = 'perf_heartbeat' AND timestamp >= today() - 14GROUP BY build_idORDER BY samples DESCNext Steps
Section titled “Next Steps”- API Overview: authentication and the query endpoint
- CLI Reference:
framedash query - Troubleshooting: confirm events arrived before you query