1use anyhow::Result;
10use rusqlite::Connection;
11
12use super::TargetClass;
13
14pub const SCHEMA_VERSION: i64 = 2;
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
178CREATE TABLE IF NOT EXISTS insn_hits (
179 id INTEGER PRIMARY KEY,
180 session_id TEXT NOT NULL REFERENCES sessions(id),
181 target TEXT NOT NULL,
182 hit_count INTEGER NOT NULL,
183 sample_basis TEXT NOT NULL,
184 sample_period INTEGER,
185 window_us REAL,
186 backend TEXT NOT NULL,
187 collected_at TEXT NOT NULL,
188 detail_json TEXT
189);
190CREATE INDEX IF NOT EXISTS idx_insn_hits_target ON insn_hits(session_id, target);
191
192CREATE TABLE IF NOT EXISTS insn_hit_details (
193 id INTEGER PRIMARY KEY,
194 insn_hit_id INTEGER NOT NULL REFERENCES insn_hits(id),
195 ts_us REAL,
196 stack_json TEXT,
197 regs_json TEXT
198);
199CREATE INDEX IF NOT EXISTS idx_insn_hit_details ON insn_hit_details(insn_hit_id);
200";
201
202pub const GPU_DDL: &str = "
205CREATE TABLE IF NOT EXISTS launches (
206 id INTEGER PRIMARY KEY,
207 session_id TEXT NOT NULL REFERENCES sessions(id),
208 kernel_name TEXT NOT NULL,
209 duration_us REAL NOT NULL,
210 grid_x INTEGER, grid_y INTEGER, grid_z INTEGER,
211 block_x INTEGER, block_y INTEGER, block_z INTEGER,
212 stream_id INTEGER,
213 start_us REAL,
214 correlation_id INTEGER,
215 layer_id INTEGER REFERENCES layers(id)
216);
217CREATE INDEX IF NOT EXISTS idx_launches_kernel ON launches(session_id, kernel_name);
218CREATE INDEX IF NOT EXISTS idx_launches_start ON launches(session_id, start_us);
219CREATE INDEX IF NOT EXISTS idx_launches_stream ON launches(session_id, stream_id);
220
221CREATE TABLE IF NOT EXISTS metrics (
222 session_id TEXT NOT NULL REFERENCES sessions(id),
223 kernel_name TEXT NOT NULL,
224 occupancy_pct REAL,
225 compute_throughput_pct REAL,
226 memory_throughput_pct REAL,
227 registers_per_thread INTEGER,
228 shared_mem_static_bytes INTEGER,
229 shared_mem_dynamic_bytes INTEGER,
230 l2_hit_rate_pct REAL,
231 achieved_bandwidth_gb_s REAL,
232 peak_bandwidth_gb_s REAL,
233 boundedness TEXT,
234 layer_id INTEGER REFERENCES layers(id),
235 PRIMARY KEY (session_id, kernel_name)
236);
237
238CREATE TABLE IF NOT EXISTS transfers (
239 id INTEGER PRIMARY KEY,
240 session_id TEXT NOT NULL REFERENCES sessions(id),
241 kind TEXT NOT NULL,
242 bytes INTEGER,
243 duration_us REAL,
244 start_us REAL,
245 stream_id INTEGER,
246 layer_id INTEGER REFERENCES layers(id)
247);
248
249CREATE TABLE IF NOT EXISTS ops (
250 id INTEGER PRIMARY KEY,
251 session_id TEXT NOT NULL REFERENCES sessions(id),
252 name TEXT NOT NULL,
253 module_path TEXT,
254 cpu_time_us REAL,
255 gpu_time_us REAL,
256 input_shapes TEXT,
257 layer_id INTEGER REFERENCES layers(id)
258);
259
260CREATE TABLE IF NOT EXISTS op_kernel_map (
261 session_id TEXT NOT NULL REFERENCES sessions(id),
262 op_id INTEGER NOT NULL REFERENCES ops(id),
263 kernel_name TEXT NOT NULL,
264 PRIMARY KEY (op_id, kernel_name)
265);
266";
267
268pub const CPU_DDL: &str = "
271CREATE TABLE IF NOT EXISTS samples (
272 id INTEGER PRIMARY KEY,
273 session_id TEXT NOT NULL REFERENCES sessions(id),
274 symbol_id INTEGER REFERENCES symbols(id),
275 thread TEXT,
276 start_us REAL,
277 duration_us REAL,
278 cpu_ns INTEGER,
279 weight REAL NOT NULL DEFAULT 1.0,
280 stack_json TEXT,
281 layer_id INTEGER REFERENCES layers(id)
282);
283CREATE INDEX IF NOT EXISTS idx_samples_sym ON samples(session_id, symbol_id);
284CREATE INDEX IF NOT EXISTS idx_samples_start ON samples(session_id, start_us);
285
286CREATE TABLE IF NOT EXISTS counters (
287 id INTEGER PRIMARY KEY,
288 session_id TEXT NOT NULL REFERENCES sessions(id),
289 name TEXT NOT NULL,
290 symbol_id INTEGER REFERENCES symbols(id),
291 value REAL NOT NULL,
292 unit TEXT,
293 layer_id INTEGER REFERENCES layers(id)
294);
295CREATE INDEX IF NOT EXISTS idx_counters_name ON counters(session_id, name);
296";
297
298pub const MANAGED_DDL: &str = "
300CREATE TABLE IF NOT EXISTS gc_events (
301 id INTEGER PRIMARY KEY,
302 session_id TEXT NOT NULL REFERENCES sessions(id),
303 kind TEXT NOT NULL,
304 pause_us REAL,
305 start_us REAL,
306 heap_before_bytes INTEGER,
307 heap_after_bytes INTEGER,
308 reason TEXT,
309 layer_id INTEGER REFERENCES layers(id)
310);
311CREATE INDEX IF NOT EXISTS idx_gc_start ON gc_events(session_id, start_us);
312
313CREATE TABLE IF NOT EXISTS jit_events (
314 id INTEGER PRIMARY KEY,
315 session_id TEXT NOT NULL REFERENCES sessions(id),
316 symbol_id INTEGER REFERENCES symbols(id),
317 compile_us REAL,
318 code_bytes INTEGER,
319 tier TEXT,
320 start_us REAL,
321 layer_id INTEGER REFERENCES layers(id)
322);
323";
324
325pub const PYTHON_DDL: &str = "
327CREATE TABLE IF NOT EXISTS gil_events (
328 id INTEGER PRIMARY KEY,
329 session_id TEXT NOT NULL REFERENCES sessions(id),
330 kind TEXT NOT NULL,
331 thread TEXT,
332 start_us REAL,
333 duration_us REAL,
334 layer_id INTEGER REFERENCES layers(id)
335);
336";
337
338pub const NODE_DDL: &str = "
340CREATE TABLE IF NOT EXISTS event_loop_lags (
341 id INTEGER PRIMARY KEY,
342 session_id TEXT NOT NULL REFERENCES sessions(id),
343 lag_us REAL NOT NULL,
344 start_us REAL,
345 phase TEXT,
346 layer_id INTEGER REFERENCES layers(id)
347);
348";
349
350pub fn apply(conn: &Connection, class: TargetClass) -> Result<()> {
355 conn.execute_batch(CORE_DDL)?;
356 conn.execute_batch(DEBUG_DDL)?;
357 conn.execute_batch(CROSSTRACK_DDL)?;
358 match class {
359 TargetClass::Gpu => conn.execute_batch(GPU_DDL)?,
360 TargetClass::NativeCpu => conn.execute_batch(CPU_DDL)?,
361 TargetClass::ManagedDotnet | TargetClass::Jvm => {
362 conn.execute_batch(CPU_DDL)?;
363 conn.execute_batch(MANAGED_DDL)?;
364 }
365 TargetClass::Python => {
366 conn.execute_batch(CPU_DDL)?;
367 conn.execute_batch(PYTHON_DDL)?;
368 }
369 TargetClass::JsNode => {
370 conn.execute_batch(CPU_DDL)?;
371 conn.execute_batch(NODE_DDL)?;
372 }
373 TargetClass::Ruby | TargetClass::Php => {
377 conn.execute_batch(CPU_DDL)?;
378 }
379 }
380 Ok(())
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 fn in_mem() -> Connection {
388 Connection::open_in_memory().unwrap()
389 }
390
391 #[test]
392 fn applies_cleanly_per_class() {
393 for class in [
394 TargetClass::Gpu,
395 TargetClass::NativeCpu,
396 TargetClass::ManagedDotnet,
397 TargetClass::Jvm,
398 TargetClass::Python,
399 TargetClass::JsNode,
400 TargetClass::Ruby,
401 TargetClass::Php,
402 ] {
403 let c = in_mem();
404 apply(&c, class).unwrap_or_else(|e| panic!("apply({class:?}): {e}"));
405 }
406 }
407
408 #[test]
409 fn apply_is_idempotent() {
410 let c = in_mem();
411 apply(&c, TargetClass::Gpu).unwrap();
412 apply(&c, TargetClass::Gpu).unwrap();
413 apply(&c, TargetClass::Gpu).unwrap();
414 }
415
416 #[test]
417 fn core_tables_exist_after_apply() {
418 let c = in_mem();
419 apply(&c, TargetClass::NativeCpu).unwrap();
420 for t in ["sessions", "layers", "symbols", "meta", "failures",
421 "regions", "allocations",
422 "commands", "breakpoint_hits", "watch_evals",
423 "disassembly", "source_snapshots", "alloc_sites",
424 "insn_hits", "insn_hit_details",
425 "samples", "counters"] {
426 let exists: i64 = c.query_row(
427 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?1",
428 [t],
429 |r| r.get(0),
430 ).unwrap();
431 assert_eq!(exists, 1, "missing table: {t}");
432 }
433 }
434
435 #[test]
436 fn gpu_class_has_launches_but_not_samples() {
437 let c = in_mem();
438 apply(&c, TargetClass::Gpu).unwrap();
439 let launches: i64 = c.query_row(
440 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='launches'",
441 [], |r| r.get(0)).unwrap();
442 assert_eq!(launches, 1);
443 let samples: i64 = c.query_row(
444 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='samples'",
445 [], |r| r.get(0)).unwrap();
446 assert_eq!(samples, 0, "samples table should not exist for GPU class");
447 }
448}