Analyzing systing traces
Systing stores traces in DuckDB. The systing-analyze MCP server exposes structured tools to query them. This skill tells you which tool to reach for and how the data is laid out.
Recommended workflow
trace_info — Always start here. Pass the path to the .duckdb file. Returns trace IDs, time range, available tables/row counts, and top processes. This also caches the DB so later calls can omit path.
list_tables / describe_table — Discover schema for ad-hoc queries.
- High-level tools for common questions — see below.
query — For anything the high-level tools don't cover, write SQL. Results cap at 10k rows; use LIMIT/OFFSET for more.
Tool cheatsheet
| Question |
Tool |
Notes |
| What's in this trace? |
trace_info |
First call; pass path |
| Where is CPU time going? |
flamegraph |
filter stack_type=cpu; optionally filter by pid or tid |
| Why is the process blocked / off-CPU? |
flamegraph |
filter stack_type=uninterruptible (D-state) or interruptible (S-state) |
| Is the scheduler oversubscribed? Latency? |
sched_stats |
no filter = whole-trace; pid = per-thread breakdown; tid = single thread |
| Which CPUs are busy / idle? |
cpu_stats |
per-CPU utilization, IRQ time, runqueue depth |
| What's the network doing? |
network_connections |
per-connection bytes, retransmit rate |
| Interface-level network? |
network_interfaces |
per-interface, per-protocol breakdown |
| Both sides of a connection (multi-node)? |
network_socket_pairs |
matched socket pairs across traces |
| What ran on the TPU? Duty cycle? HBM? |
query |
no dedicated tool yet — see TPU schema below |
| Anything else |
query |
raw SQL; see schema below |
Key schema for query
Timestamps
All ts columns are nanoseconds from an arbitrary epoch. Convert durations: dur / 1e6 → ms, dur / 1e9 → sec.
Thread / process identity
utid / upid are internal IDs (dense, unique within DB).
- Join to
thread (utid → tid, name, upid) and process (upid → pid, name) for the Linux IDs.
Stack traces
Two representations exist:
Flattened (easier) — stack table: one row per sampled stack with frame_names as an array (leaf-to-root order). Joins to stack_sample on stack_id.
-- Top 10 hottest leaf functions (on-CPU samples)
SELECT frame_names[1] AS leaf, count(*) AS samples
FROM stack_sample JOIN stack USING (stack_id)
WHERE stack_event_type = 1 -- 1=cpu, 0=uninterruptible, 2=interruptible
GROUP BY 1 ORDER BY 2 DESC LIMIT 10;
Normalized (Perfetto-style) — perf_sample → stack_profile_callsite (parent-child tree) → stack_profile_frame → stack_profile_symbol. Use when you need mapping/build-id info. Walk the parent_id chain to reconstruct stacks.
Scheduling
sched_slice: one row per scheduled slice.
ts, dur — nanoseconds
utid, cpu
end_state: 1=S (interruptible sleep), 2=D (uninterruptible sleep), others = preempted/running
end_state_str — human-readable state string
-- Longest uninterruptible-sleep episodes
SELECT t.name, ss.dur/1e6 AS ms, ss.ts
FROM sched_slice ss JOIN thread t USING (utid)
WHERE end_state = 2
ORDER BY dur DESC LIMIT 20;
Network
network_syscall — sendmsg/recvmsg calls: ts, dur, utid, event_type, socket_id, bytes, buffer usage.
network_packet — packet-level: seq, length, is_retransmit, srtt_ms, drop_reason_str, tcp_flags.
network_socket — socket metadata: socket_id, protocol, src/dest IP:port.
- Join syscall/packet → socket on
socket_id.
-- Retransmits by connection
SELECT s.src_ip, s.src_port, s.dest_ip, s.dest_port,
count(*) FILTER (WHERE p.is_retransmit) AS retransmits,
count(*) AS total_packets
FROM network_packet p JOIN network_socket s USING (socket_id)
GROUP BY 1,2,3,4 HAVING retransmits > 0
ORDER BY retransmits DESC;
TPU
No dedicated MCP tool yet — use query. Three tables:
tpu_device — one row per TPU core. id (join key), device_ordinal, chip_id, core_id, hostname, device_type, topology_{x,y,z}, clock_rate_ghz, hbm_size_bytes, hbm_bandwidth_gbps.
tpu_op — XLA op execution slices (from --tpu-profile). ts, dur (ns), tpu_device_id (FK → tpu_device.id), op_name, category, stream, group_id, flops, bytes_accessed, bytes_hbm, bytes_cmem, bytes_vmem.
tpu_metric — polled runtime counters (from --tpu-metrics). ts, device_id (ordinal), metric_name, value. Default metrics: tpu.runtime.tensorcore.dutycycle.percent, tpu.runtime.hbm.memory.usage.bytes.
-- Top 20 TPU ops by total device time
SELECT op_name, category,
count(*) AS calls,
sum(dur)/1e6 AS total_ms,
avg(dur)/1e3 AS avg_us,
sum(flops) AS total_flops,
sum(bytes_hbm) AS total_hbm_bytes
FROM tpu_op
GROUP BY 1,2 ORDER BY total_ms DESC LIMIT 20;
-- TPU utilization (duty cycle) over time, per device
SELECT device_id, ts, value AS dutycycle_pct
FROM tpu_metric
WHERE metric_name = 'tpu.runtime.tensorcore.dutycycle.percent'
ORDER BY device_id, ts;
-- Correlate TPU gaps with host-side blocking: find intervals where no TPU op
-- is running for >1ms and check sched_slice for what the host thread was doing
WITH gaps AS (
SELECT ts + dur AS gap_start,
lead(ts) OVER (PARTITION BY tpu_device_id ORDER BY ts) AS gap_end
FROM tpu_op
)
SELECT gap_start, (gap_end - gap_start)/1e6 AS gap_ms
FROM gaps
WHERE gap_end - gap_start > 1000000 -- >1ms
ORDER BY gap_ms DESC LIMIT 20;
Multi-trace databases
Tables have a trace_id column. Filter on it when the DB contains multiple captures.
Using flamegraph
The flamegraph tool returns folded-stack output (one line per unique stack, semicolon-separated frames root→leaf, space, sample count). Filter options:
stack_type — cpu / uninterruptible / interruptible / all
pid / tid — restrict to a process or thread
min_samples — drop noise
trace_id — for multi-trace DBs
Pipe the result into your preferred flamegraph renderer, or just scan for the heaviest stacks in the text.
Common investigation patterns
"Why is my process slow?"
sched_stats with pid — is it on-CPU (CPU-bound), or mostly sleeping (blocked)?
- If CPU-bound →
flamegraph stack_type=cpu pid=<pid> for hotspots.
- If blocked →
flamegraph stack_type=uninterruptible pid=<pid> (I/O, locks) and stack_type=interruptible (waits, timers).
"Is the machine overloaded?"
cpu_stats — look for CPUs with near-zero idle % and high runqueue depth percentiles.
sched_stats (no filter) — check preemption rates and CPU migrations.
"Network slow / dropping?"
network_connections — any connection with high retransmit rate?
query on network_packet — filter drop_reason_str IS NOT NULL for kernel drop reasons.
query on network_syscall — sort by dur to find stalled recv/send calls.
"TPU underutilized?"
query on tpu_metric — look at tensorcore.dutycycle.percent; sustained low values mean the device is starved.
query on tpu_op — find large gaps between consecutive ops on the same device (see gap query above).
- Cross-reference gap timestamps with
sched_slice / flamegraph on the host to find what the feeding process was doing (sleeping? blocked on recv? GIL?).
Arguments
If the user passed a path as an argument to this skill ($ARGUMENTS), use it as the path parameter in your first trace_info call.
Source: josefbacik/systing — distributed by TomeVault.
1---2name: systing-analyze3description: Analyze a systing trace database (.duckdb). Use when the user asks about a systing trace — flamegraphs, scheduling latency, CPU hotspots, network behavior, off-CPU time, TPU op/metric data, or any question about what's in a trace.duckdb file. Orchestrates the systing-analyze MCP tools (trace_info, query, flamegraph, sched_stats, cpu_stats, network_*). Use when this capability is needed.4---56# Analyzing systing traces78Systing stores traces in **DuckDB**. The `systing-analyze` MCP server exposes structured tools to query them. This skill tells you which tool to reach for and how the data is laid out.910## Recommended workflow11121. **`trace_info`** — Always start here. Pass the `path` to the `.duckdb` file. Returns trace IDs, time range, available tables/row counts, and top processes. This also caches the DB so later calls can omit `path`.132. **`list_tables`** / **`describe_table`** — Discover schema for ad-hoc queries.143. **High-level tools** for common questions — see below.154. **`query`** — For anything the high-level tools don't cover, write SQL. Results cap at 10k rows; use `LIMIT`/`OFFSET` for more.1617## Tool cheatsheet1819| Question | Tool | Notes |20|---|---|---|21| What's in this trace? | `trace_info` | First call; pass `path` |22| Where is CPU time going? | `flamegraph` | filter `stack_type=cpu`; optionally filter by `pid` or `tid` |23| Why is the process blocked / off-CPU? | `flamegraph` | filter `stack_type=uninterruptible` (D-state) or `interruptible` (S-state) |24| Is the scheduler oversubscribed? Latency? | `sched_stats` | no filter = whole-trace; `pid` = per-thread breakdown; `tid` = single thread |25| Which CPUs are busy / idle? | `cpu_stats` | per-CPU utilization, IRQ time, runqueue depth |26| What's the network doing? | `network_connections` | per-connection bytes, retransmit rate |27| Interface-level network? | `network_interfaces` | per-interface, per-protocol breakdown |28| Both sides of a connection (multi-node)? | `network_socket_pairs` | matched socket pairs across traces |29| What ran on the TPU? Duty cycle? HBM? | `query` | no dedicated tool yet — see TPU schema below |30| Anything else | `query` | raw SQL; see schema below |3132## Key schema for `query`3334### Timestamps35All `ts` columns are **nanoseconds** from an arbitrary epoch. Convert durations: `dur / 1e6` → ms, `dur / 1e9` → sec.3637### Thread / process identity38- `utid` / `upid` are **internal** IDs (dense, unique within DB).39- Join to `thread` (utid → tid, name, upid) and `process` (upid → pid, name) for the Linux IDs.4041### Stack traces42Two representations exist:4344**Flattened (easier)** — `stack` table: one row per sampled stack with `frame_names` as an array (leaf-to-root order). Joins to `stack_sample` on `stack_id`.45```sql46-- Top 10 hottest leaf functions (on-CPU samples)47SELECT frame_names[1] AS leaf, count(*) AS samples48FROM stack_sample JOIN stack USING (stack_id)49WHERE stack_event_type = 1 -- 1=cpu, 0=uninterruptible, 2=interruptible50GROUP BY 1 ORDER BY 2 DESC LIMIT 10;51```5253**Normalized (Perfetto-style)** — `perf_sample` → `stack_profile_callsite` (parent-child tree) → `stack_profile_frame` → `stack_profile_symbol`. Use when you need mapping/build-id info. Walk the `parent_id` chain to reconstruct stacks.5455### Scheduling56`sched_slice`: one row per scheduled slice.57- `ts`, `dur` — nanoseconds58- `utid`, `cpu`59- `end_state`: `1`=S (interruptible sleep), `2`=D (uninterruptible sleep), others = preempted/running60- `end_state_str` — human-readable state string6162```sql63-- Longest uninterruptible-sleep episodes64SELECT t.name, ss.dur/1e6 AS ms, ss.ts65FROM sched_slice ss JOIN thread t USING (utid)66WHERE end_state = 267ORDER BY dur DESC LIMIT 20;68```6970### Network71- `network_syscall` — sendmsg/recvmsg calls: `ts`, `dur`, `utid`, `event_type`, `socket_id`, `bytes`, buffer usage.72- `network_packet` — packet-level: `seq`, `length`, `is_retransmit`, `srtt_ms`, `drop_reason_str`, `tcp_flags`.73- `network_socket` — socket metadata: `socket_id`, `protocol`, src/dest IP:port.74- Join syscall/packet → socket on `socket_id`.7576```sql77-- Retransmits by connection78SELECT s.src_ip, s.src_port, s.dest_ip, s.dest_port,79 count(*) FILTER (WHERE p.is_retransmit) AS retransmits,80 count(*) AS total_packets81FROM network_packet p JOIN network_socket s USING (socket_id)82GROUP BY 1,2,3,4 HAVING retransmits > 083ORDER BY retransmits DESC;84```8586### TPU87No dedicated MCP tool yet — use `query`. Three tables:8889- **`tpu_device`** — one row per TPU core. `id` (join key), `device_ordinal`, `chip_id`, `core_id`, `hostname`, `device_type`, `topology_{x,y,z}`, `clock_rate_ghz`, `hbm_size_bytes`, `hbm_bandwidth_gbps`.90- **`tpu_op`** — XLA op execution slices (from `--tpu-profile`). `ts`, `dur` (ns), `tpu_device_id` (FK → tpu_device.id), `op_name`, `category`, `stream`, `group_id`, `flops`, `bytes_accessed`, `bytes_hbm`, `bytes_cmem`, `bytes_vmem`.91- **`tpu_metric`** — polled runtime counters (from `--tpu-metrics`). `ts`, `device_id` (ordinal), `metric_name`, `value`. Default metrics: `tpu.runtime.tensorcore.dutycycle.percent`, `tpu.runtime.hbm.memory.usage.bytes`.9293```sql94-- Top 20 TPU ops by total device time95SELECT op_name, category,96 count(*) AS calls,97 sum(dur)/1e6 AS total_ms,98 avg(dur)/1e3 AS avg_us,99 sum(flops) AS total_flops,100 sum(bytes_hbm) AS total_hbm_bytes101FROM tpu_op102GROUP BY 1,2 ORDER BY total_ms DESC LIMIT 20;103104-- TPU utilization (duty cycle) over time, per device105SELECT device_id, ts, value AS dutycycle_pct106FROM tpu_metric107WHERE metric_name = 'tpu.runtime.tensorcore.dutycycle.percent'108ORDER BY device_id, ts;109110-- Correlate TPU gaps with host-side blocking: find intervals where no TPU op111-- is running for >1ms and check sched_slice for what the host thread was doing112WITH gaps AS (113 SELECT ts + dur AS gap_start,114 lead(ts) OVER (PARTITION BY tpu_device_id ORDER BY ts) AS gap_end115 FROM tpu_op116)117SELECT gap_start, (gap_end - gap_start)/1e6 AS gap_ms118FROM gaps119WHERE gap_end - gap_start > 1000000 -- >1ms120ORDER BY gap_ms DESC LIMIT 20;121```122123### Multi-trace databases124Tables have a `trace_id` column. Filter on it when the DB contains multiple captures.125126## Using `flamegraph`127128The `flamegraph` tool returns folded-stack output (one line per unique stack, semicolon-separated frames root→leaf, space, sample count). Filter options:129- `stack_type` — `cpu` / `uninterruptible` / `interruptible` / `all`130- `pid` / `tid` — restrict to a process or thread131- `min_samples` — drop noise132- `trace_id` — for multi-trace DBs133134Pipe the result into your preferred flamegraph renderer, or just scan for the heaviest stacks in the text.135136## Common investigation patterns137138**"Why is my process slow?"**1391. `sched_stats` with `pid` — is it on-CPU (CPU-bound), or mostly sleeping (blocked)?1402. If CPU-bound → `flamegraph stack_type=cpu pid=<pid>` for hotspots.1413. If blocked → `flamegraph stack_type=uninterruptible pid=<pid>` (I/O, locks) and `stack_type=interruptible` (waits, timers).142143**"Is the machine overloaded?"**1441. `cpu_stats` — look for CPUs with near-zero idle % and high runqueue depth percentiles.1452. `sched_stats` (no filter) — check preemption rates and CPU migrations.146147**"Network slow / dropping?"**1481. `network_connections` — any connection with high retransmit rate?1492. `query` on `network_packet` — filter `drop_reason_str IS NOT NULL` for kernel drop reasons.1503. `query` on `network_syscall` — sort by `dur` to find stalled recv/send calls.151152**"TPU underutilized?"**1531. `query` on `tpu_metric` — look at `tensorcore.dutycycle.percent`; sustained low values mean the device is starved.1542. `query` on `tpu_op` — find large gaps between consecutive ops on the same device (see gap query above).1553. Cross-reference gap timestamps with `sched_slice` / `flamegraph` on the host to find what the feeding process was doing (sleeping? blocked on recv? GIL?).156157## Arguments158159If the user passed a path as an argument to this skill (`$ARGUMENTS`), use it as the `path` parameter in your first `trace_info` call.160161---162> Source: [josefbacik/systing](https://github.com/josefbacik/systing) — distributed by [TomeVault](https://tomevault.io).163<!-- tomevault:4.0:skill_md:2026-06-29 -->