1use anyhow::Result;
10use rusqlite::Connection;
11
12use super::TargetClass;
13
14pub const SCHEMA_VERSION: i64 = 1;
16
17pub const CORE_DDL: &str = "
19CREATE TABLE IF NOT EXISTS sessions (
20 id TEXT PRIMARY KEY,
21 kind TEXT NOT NULL CHECK (kind IN ('debug','profile')),
22 target TEXT NOT NULL,
23 target_class TEXT NOT NULL,
24 target_hash TEXT,
25 started_at TEXT NOT NULL,
26 ended_at TEXT,
27 label TEXT NOT NULL,
28 created_by TEXT NOT NULL DEFAULT 'auto'
29);
30
31CREATE TABLE IF NOT EXISTS layers (
32 id INTEGER PRIMARY KEY,
33 session_id TEXT NOT NULL REFERENCES sessions(id),
34 source TEXT NOT NULL,
35 file TEXT,
36 collected_at TEXT,
37 command_used TEXT,
38 collection_secs REAL,
39 target_hash TEXT
40);
41CREATE INDEX IF NOT EXISTS idx_layers_session ON layers(session_id);
42
43CREATE TABLE IF NOT EXISTS symbols (
44 id INTEGER PRIMARY KEY,
45 session_id TEXT NOT NULL REFERENCES sessions(id),
46 lang TEXT NOT NULL,
47 fqn TEXT NOT NULL,
48 file TEXT,
49 line INTEGER,
50 demangled TEXT,
51 raw TEXT NOT NULL,
52 is_synthetic INTEGER NOT NULL DEFAULT 0,
53 UNIQUE(session_id, lang, fqn)
54);
55CREATE INDEX IF NOT EXISTS idx_symbols_fqn ON symbols(lang, fqn);
56
57CREATE TABLE IF NOT EXISTS meta (
58 session_id TEXT NOT NULL REFERENCES sessions(id),
59 key TEXT NOT NULL,
60 value TEXT,
61 PRIMARY KEY (session_id, key)
62);
63
64CREATE TABLE IF NOT EXISTS failures (
65 session_id TEXT NOT NULL REFERENCES sessions(id),
66 phase TEXT,
67 error TEXT
68);
69
70CREATE TABLE IF NOT EXISTS regions (
71 id INTEGER PRIMARY KEY,
72 session_id TEXT NOT NULL REFERENCES sessions(id),
73 name TEXT NOT NULL,
74 start_us REAL,
75 duration_us REAL,
76 thread TEXT,
77 layer_id INTEGER REFERENCES layers(id)
78);
79CREATE INDEX IF NOT EXISTS idx_regions_session ON regions(session_id);
80
81CREATE TABLE IF NOT EXISTS allocations (
82 id INTEGER PRIMARY KEY,
83 session_id TEXT NOT NULL REFERENCES sessions(id),
84 op TEXT NOT NULL,
85 address INTEGER NOT NULL,
86 bytes INTEGER NOT NULL,
87 start_us REAL,
88 heap TEXT NOT NULL DEFAULT 'default',
89 thread TEXT,
90 stack_json TEXT,
91 layer_id INTEGER REFERENCES layers(id)
92);
93CREATE INDEX IF NOT EXISTS idx_alloc_addr ON allocations(session_id, address);
94CREATE INDEX IF NOT EXISTS idx_alloc_time ON allocations(session_id, start_us);
95";
96
97pub const DEBUG_DDL: &str = "
100CREATE TABLE IF NOT EXISTS commands (
101 seq INTEGER PRIMARY KEY,
102 session_id TEXT NOT NULL REFERENCES sessions(id),
103 input TEXT NOT NULL,
104 output_head TEXT,
105 output_file TEXT,
106 output_bytes INTEGER,
107 ts TEXT NOT NULL,
108 canonical_op TEXT
109);
110CREATE INDEX IF NOT EXISTS idx_commands_session ON commands(session_id);
111
112CREATE TABLE IF NOT EXISTS breakpoint_hits (
113 id INTEGER PRIMARY KEY,
114 session_id TEXT NOT NULL REFERENCES sessions(id),
115 location_key TEXT NOT NULL,
116 hit_seq INTEGER NOT NULL,
117 thread TEXT,
118 ts TEXT NOT NULL,
119 locals_json TEXT,
120 stack_json TEXT
121);
122CREATE INDEX IF NOT EXISTS idx_bp_location ON breakpoint_hits(session_id, location_key);
123
124CREATE TABLE IF NOT EXISTS watch_evals (
125 id INTEGER PRIMARY KEY,
126 session_id TEXT NOT NULL REFERENCES sessions(id),
127 hit_id INTEGER REFERENCES breakpoint_hits(id),
128 expr TEXT NOT NULL,
129 value TEXT,
130 type_name TEXT,
131 ts TEXT NOT NULL
132);
133";
134
135pub const CROSSTRACK_DDL: &str = "
138CREATE TABLE IF NOT EXISTS disassembly (
139 id INTEGER PRIMARY KEY,
140 session_id TEXT NOT NULL REFERENCES sessions(id),
141 symbol_id INTEGER REFERENCES symbols(id),
142 source TEXT NOT NULL,
143 tier TEXT,
144 code_bytes INTEGER,
145 asm_text TEXT NOT NULL,
146 asm_lines_json TEXT,
147 collected_at TEXT NOT NULL,
148 trigger TEXT,
149 layer_id INTEGER REFERENCES layers(id)
150);
151CREATE INDEX IF NOT EXISTS idx_disasm_sym ON disassembly(symbol_id);
152
153CREATE TABLE IF NOT EXISTS source_snapshots (
154 id INTEGER PRIMARY KEY,
155 session_id TEXT NOT NULL REFERENCES sessions(id),
156 symbol_id INTEGER REFERENCES symbols(id),
157 file TEXT,
158 line_start INTEGER,
159 line_end INTEGER,
160 text TEXT,
161 content_hash TEXT,
162 collected_at TEXT NOT NULL
163);
164CREATE INDEX IF NOT EXISTS idx_src_sym ON source_snapshots(symbol_id);
165
166CREATE TABLE IF NOT EXISTS alloc_sites (
167 id INTEGER PRIMARY KEY,
168 session_id TEXT NOT NULL REFERENCES sessions(id),
169 symbol_id INTEGER REFERENCES symbols(id),
170 bytes_total INTEGER,
171 count INTEGER,
172 largest_bytes INTEGER,
173 collected_at TEXT NOT NULL,
174 layer_id INTEGER REFERENCES layers(id)
175);
176CREATE INDEX IF NOT EXISTS idx_alloc_site_sym ON alloc_sites(symbol_id);
177";
178
179pub const GPU_DDL: &str = "
182CREATE TABLE IF NOT EXISTS launches (
183 id INTEGER PRIMARY KEY,
184 session_id TEXT NOT NULL REFERENCES sessions(id),
185 kernel_name TEXT NOT NULL,
186 duration_us REAL NOT NULL,
187 grid_x INTEGER, grid_y INTEGER, grid_z INTEGER,
188 block_x INTEGER, block_y INTEGER, block_z INTEGER,
189 stream_id INTEGER,
190 start_us REAL,
191 correlation_id INTEGER,
192 layer_id INTEGER REFERENCES layers(id)
193);
194CREATE INDEX IF NOT EXISTS idx_launches_kernel ON launches(session_id, kernel_name);
195CREATE INDEX IF NOT EXISTS idx_launches_start ON launches(session_id, start_us);
196CREATE INDEX IF NOT EXISTS idx_launches_stream ON launches(session_id, stream_id);
197
198CREATE TABLE IF NOT EXISTS metrics (
199 session_id TEXT NOT NULL REFERENCES sessions(id),
200 kernel_name TEXT NOT NULL,
201 occupancy_pct REAL,
202 compute_throughput_pct REAL,
203 memory_throughput_pct REAL,
204 registers_per_thread INTEGER,
205 shared_mem_static_bytes INTEGER,
206 shared_mem_dynamic_bytes INTEGER,
207 l2_hit_rate_pct REAL,
208 achieved_bandwidth_gb_s REAL,
209 peak_bandwidth_gb_s REAL,
210 boundedness TEXT,
211 layer_id INTEGER REFERENCES layers(id),
212 PRIMARY KEY (session_id, kernel_name)
213);
214
215CREATE TABLE IF NOT EXISTS transfers (
216 id INTEGER PRIMARY KEY,
217 session_id TEXT NOT NULL REFERENCES sessions(id),
218 kind TEXT NOT NULL,
219 bytes INTEGER,
220 duration_us REAL,
221 start_us REAL,
222 stream_id INTEGER,
223 layer_id INTEGER REFERENCES layers(id)
224);
225
226CREATE TABLE IF NOT EXISTS ops (
227 id INTEGER PRIMARY KEY,
228 session_id TEXT NOT NULL REFERENCES sessions(id),
229 name TEXT NOT NULL,
230 module_path TEXT,
231 cpu_time_us REAL,
232 gpu_time_us REAL,
233 input_shapes TEXT,
234 layer_id INTEGER REFERENCES layers(id)
235);
236
237CREATE TABLE IF NOT EXISTS op_kernel_map (
238 session_id TEXT NOT NULL REFERENCES sessions(id),
239 op_id INTEGER NOT NULL REFERENCES ops(id),
240 kernel_name TEXT NOT NULL,
241 PRIMARY KEY (op_id, kernel_name)
242);
243";
244
245pub const CPU_DDL: &str = "
248CREATE TABLE IF NOT EXISTS samples (
249 id INTEGER PRIMARY KEY,
250 session_id TEXT NOT NULL REFERENCES sessions(id),
251 symbol_id INTEGER REFERENCES symbols(id),
252 thread TEXT,
253 start_us REAL,
254 duration_us REAL,
255 cpu_ns INTEGER,
256 weight REAL NOT NULL DEFAULT 1.0,
257 stack_json TEXT,
258 layer_id INTEGER REFERENCES layers(id)
259);
260CREATE INDEX IF NOT EXISTS idx_samples_sym ON samples(session_id, symbol_id);
261CREATE INDEX IF NOT EXISTS idx_samples_start ON samples(session_id, start_us);
262
263CREATE TABLE IF NOT EXISTS counters (
264 id INTEGER PRIMARY KEY,
265 session_id TEXT NOT NULL REFERENCES sessions(id),
266 name TEXT NOT NULL,
267 symbol_id INTEGER REFERENCES symbols(id),
268 value REAL NOT NULL,
269 unit TEXT,
270 layer_id INTEGER REFERENCES layers(id)
271);
272CREATE INDEX IF NOT EXISTS idx_counters_name ON counters(session_id, name);
273";
274
275pub const MANAGED_DDL: &str = "
277CREATE TABLE IF NOT EXISTS gc_events (
278 id INTEGER PRIMARY KEY,
279 session_id TEXT NOT NULL REFERENCES sessions(id),
280 kind TEXT NOT NULL,
281 pause_us REAL,
282 start_us REAL,
283 heap_before_bytes INTEGER,
284 heap_after_bytes INTEGER,
285 reason TEXT,
286 layer_id INTEGER REFERENCES layers(id)
287);
288CREATE INDEX IF NOT EXISTS idx_gc_start ON gc_events(session_id, start_us);
289
290CREATE TABLE IF NOT EXISTS jit_events (
291 id INTEGER PRIMARY KEY,
292 session_id TEXT NOT NULL REFERENCES sessions(id),
293 symbol_id INTEGER REFERENCES symbols(id),
294 compile_us REAL,
295 code_bytes INTEGER,
296 tier TEXT,
297 start_us REAL,
298 layer_id INTEGER REFERENCES layers(id)
299);
300";
301
302pub const PYTHON_DDL: &str = "
304CREATE TABLE IF NOT EXISTS gil_events (
305 id INTEGER PRIMARY KEY,
306 session_id TEXT NOT NULL REFERENCES sessions(id),
307 kind TEXT NOT NULL,
308 thread TEXT,
309 start_us REAL,
310 duration_us REAL,
311 layer_id INTEGER REFERENCES layers(id)
312);
313";
314
315pub const NODE_DDL: &str = "
317CREATE TABLE IF NOT EXISTS event_loop_lags (
318 id INTEGER PRIMARY KEY,
319 session_id TEXT NOT NULL REFERENCES sessions(id),
320 lag_us REAL NOT NULL,
321 start_us REAL,
322 phase TEXT,
323 layer_id INTEGER REFERENCES layers(id)
324);
325";
326
327pub fn apply(conn: &Connection, class: TargetClass) -> Result<()> {
332 conn.execute_batch(CORE_DDL)?;
333 conn.execute_batch(DEBUG_DDL)?;
334 conn.execute_batch(CROSSTRACK_DDL)?;
335 match class {
336 TargetClass::Gpu => conn.execute_batch(GPU_DDL)?,
337 TargetClass::NativeCpu => conn.execute_batch(CPU_DDL)?,
338 TargetClass::ManagedDotnet | TargetClass::Jvm => {
339 conn.execute_batch(CPU_DDL)?;
340 conn.execute_batch(MANAGED_DDL)?;
341 }
342 TargetClass::Python => {
343 conn.execute_batch(CPU_DDL)?;
344 conn.execute_batch(PYTHON_DDL)?;
345 }
346 TargetClass::JsNode => {
347 conn.execute_batch(CPU_DDL)?;
348 conn.execute_batch(NODE_DDL)?;
349 }
350 TargetClass::Ruby | TargetClass::Php => {
354 conn.execute_batch(CPU_DDL)?;
355 }
356 }
357 Ok(())
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 fn in_mem() -> Connection {
365 Connection::open_in_memory().unwrap()
366 }
367
368 #[test]
369 fn applies_cleanly_per_class() {
370 for class in [
371 TargetClass::Gpu,
372 TargetClass::NativeCpu,
373 TargetClass::ManagedDotnet,
374 TargetClass::Jvm,
375 TargetClass::Python,
376 TargetClass::JsNode,
377 TargetClass::Ruby,
378 TargetClass::Php,
379 ] {
380 let c = in_mem();
381 apply(&c, class).unwrap_or_else(|e| panic!("apply({class:?}): {e}"));
382 }
383 }
384
385 #[test]
386 fn apply_is_idempotent() {
387 let c = in_mem();
388 apply(&c, TargetClass::Gpu).unwrap();
389 apply(&c, TargetClass::Gpu).unwrap();
390 apply(&c, TargetClass::Gpu).unwrap();
391 }
392
393 #[test]
394 fn core_tables_exist_after_apply() {
395 let c = in_mem();
396 apply(&c, TargetClass::NativeCpu).unwrap();
397 for t in ["sessions", "layers", "symbols", "meta", "failures",
398 "regions", "allocations",
399 "commands", "breakpoint_hits", "watch_evals",
400 "disassembly", "source_snapshots", "alloc_sites",
401 "samples", "counters"] {
402 let exists: i64 = c.query_row(
403 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?1",
404 [t],
405 |r| r.get(0),
406 ).unwrap();
407 assert_eq!(exists, 1, "missing table: {t}");
408 }
409 }
410
411 #[test]
412 fn gpu_class_has_launches_but_not_samples() {
413 let c = in_mem();
414 apply(&c, TargetClass::Gpu).unwrap();
415 let launches: i64 = c.query_row(
416 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='launches'",
417 [], |r| r.get(0)).unwrap();
418 assert_eq!(launches, 1);
419 let samples: i64 = c.query_row(
420 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='samples'",
421 [], |r| r.get(0)).unwrap();
422 assert_eq!(samples, 0, "samples table should not exist for GPU class");
423 }
424}