ViewAlyzer CLI (headless)
viewalyzer-cli is the command-line front of ViewAlyzer: everything the GUI can do with a target or a recording, as one process per job with JSON on stdout. The GUI binary forwards viewalyzer --headless ... and any command verb to the same code, so hosts can spawn either binary.
host / script / agent --argv--> viewalyzer-cli <command> [flags] --> JSON on stdout
|
v
<recording>.vadb (SQLite; also queryable directly)
Two spellings reach the same commands. The verb form is the primary one; the --headless flag form is contract for existing hosts (the Python SDK, BKPT Studio, CI harnesses) and every entry below keeps working:
| VERB FORM | --HEADLESS FLAG FORM |
|---|---|
viewalyzer-cli capture --config b.vacf --output run.vadb --duration 10 | viewalyzer --headless --config b.vacf --output run.vadb --duration 10 |
viewalyzer-cli probes | viewalyzer --headless --list-probes |
viewalyzer-cli snapshot --config b.vacf --output crash.vadb | viewalyzer --headless --snapshot --config b.vacf --output crash.vadb |
viewalyzer-cli query timeline --recording <id> --tier summary | viewalyzer --headless --query timeline --recording <id> --tier summary |
viewalyzer-cli replay run.vadb --perf | viewalyzer --headless --replay run.vadb --perf |
viewalyzer-cli version | viewalyzer --headless --version |
viewalyzer-cli recordings | viewalyzer --headless --list-recordings |
viewalyzer-cli memory --elf f [--map f] | viewalyzer --headless --analyze-memory --elf f [--map f] |
viewalyzer-cli symbols --elf f | viewalyzer --headless --list-symbols --elf f |
viewalyzer-cli poll ... | viewalyzer --headless --record-polls ... |
viewalyzer-cli license [get|activate <key>|validate|deactivate|install <file>] | viewalyzer --headless --get-license / --activate-license KEY / --validate-license / --deactivate-license / --install-license FILE (see docs/LICENSING.md) |
Output conventions
| CHANNEL | CONTENTS |
|---|---|
| stdout | query / list commands: exactly one JSON object. capture / snapshot: human progress lines plus the machine-parseable contracts below, then one JSON envelope as the last line. |
| stderr | --stream JSON lines during a capture (see below). |
| exit code | 0 success; 1 failure. parse-trace-domain additionally uses 2 for unreadable files or invalid arguments; see its report contract below. |
Every JSON payload carries "schema_version": 2 (2 since the hardware-trace queries and stream events were added; additive), except the bare hwtrace --dry-run image. Errors use one envelope:
{ "schema_version": 2, "error": "window_too_wide",
"message": "634 events in the window exceed the 200 row cap for budget low",
"suggestion": { "t_end_us": 31545, "or": "filter with --kinds, or raise --budget" },
"limits": { "max_rows": 200, "would_return_rows": 634 } }
Error codes: bad_arguments, bad_config, no_such_recording, bad_recording, window_too_wide, bad_sql, symbol_not_found, map_not_found, no_hw_trace, etm_not_present, empty_capture, capture_failed, snapshot_failed, empty_snapshot, reset_failed, cooldown_active (carries retry_after_s), license_file_rejected, internal.
Capture and snapshot print two stable lines hosts parse:
[headless] Recording saved: C:\...\run.vadb (2508 KB)
[headless] Recording registered: id=fc6de594af4d
[headless] ERROR: ... precedes any failure (exit 1). [headless] State: ..., [headless] t=... events ... and [capture] ... lines are progress, not contract.
Connecting to a target
Connection settings are flat kebab-case keys, either flags (--key value) or a JSON config file (.vacf) with the same names; flags win over the file. Unknown keys are errors; comment / _* keys are notes.
{
"transport": "stlink-rambuf", // stlink-swo | stlink-rambuf | stlink-rtt | jlink-swo | jlink-rtt | jlink-rambuf | udp | serial | swo-tcp
"stlink-serial": "0033004B3033510735393935",
"target-device": "STM32G474RE", // probe-rs target name
"speed-khz": 4000,
"rambuf-scan-start": 536870912, // 0x20000000
"rambuf-scan-size": 131072,
"no-reset": false // default: reset after attach, capture from boot
}
| TRANSPORT | KEYS THAT MATTER |
|---|---|
stlink-rambuf / jlink-rambuf | --rambuf-address (skips the scan) or --rambuf-scan-start/size, --rambuf-poll-ms, --no-reset |
stlink-rtt / jlink-rtt | --rtt-channel, --rtt-address (pins _SEGGER_RTT), --no-reset |
stlink-swo / jlink-swo | --cpu-clock-hz (required; or --trace-clock-hz), --swo-freq-hz (2 MHz is the safe J-Link value), --itm-port, --init-swo |
udp | --udp-ip, --udp-port, --cobs |
serial | --serial-port, --baud, --cobs (ports lists the names) |
swo-tcp | --swo-tcp-port (a simulator or debug server's raw-SWO side channel on 127.0.0.1, --swo-tcp-host to override), optional --cpu-clock-hz, --itm-port, --swo-freq-hz; recorder streams take the authoritative clock from their CLK: setup. Pass the clock explicitly for accurate hardware-only ITM/DWT timestamps |
All debug-probe transports take --target-device, --speed-khz, --stlink-serial / --jlink-serial. help prints every key with its meaning; config --config b.vacf [flags] echoes the effective configuration without touching hardware. Tool-path and server-port keys from older configs (jlink, arm-gdb, *-port, ...) are accepted and reported as "no effect": the native probe drivers (probe-rs) spawn no servers.
Hardware trace (ITM / DWT)
Hardware trace (ITM console, DWT PC sampling and data watches, exception trace, SWO load) is set up and started through this CLI, with the flags and config keys below; hosts such as BKPT Debug drive it this way. The desktop app's GUI does not offer controls to start a hardware-trace capture.
SWO transports carry the full DWT stream; rambuf and RTT transports sample the PC by polling DWT_PCSR over the debug port (--dwt --dwt-pc N; on RTT pass --cpu-clock-hz so the cadence matches). The flat keys are the C++-era spelling and still work; the nested hardware-trace block is the full Eclipse-level set:
"hardware-trace": {
"itm": { "ports": 3, "privilege": 0, "timestamps": 1 }, // ITM_TER mask (default: recorder port | port 0), ITM_TPR, TSPrescale 0|1|4|16|64
"dwt": { "enable": true, "exception-trace": true, "pc-sample-cyc": 16384,
"counters": ["cpi","exc","sleep","lsu","fold","cyc"],
"watch": [ { "addr": "0x20000410", "size": 4, "function": "data-rw", "pc": true, "name": "g_temp" } ] },
"trace-port": { "swo-hz": 2000000, "protocol": "nrz" }
}
| KEY / FLAG | MEANING |
|---|---|
--dwt, --dwt-exc, --dwt-pc N | DWT on, exception trace, PC sampling every N cycles (rounded onto the 64..16384 POSTCNT grid; the applied value lands in pc_sample_interval_cycles) |
--dwt-watch spec[,spec] | data-watch comparators (up to 4, positional): [name@]0xADDR[:size][:data-rw|data-r|data-w|pc|address][:pc]; default data-w, size 4; :pc adds the PC of the access |
--dwt-counters cpi,exc,sleep,lsu,fold,cyc | event counters (cyc is refused while PC sampling is on: both use POSTCNT) |
--itm-ports, --itm-privilege, --itm-timestamps off|1|4|16|64 | stimulus port mask (bit n = port n; the recorder port and port 0 by default, so a firmware printf on port 0 keeps flowing), privilege mask, timestamp prescaler |
--dwt-path auto|poll|swo | poll forces DWT_PCSR polling on an SWO session (SWO-borne sources off) |
--hardware-trace <json | @file> | the whole block at once |
Every write goes through one register recipe shared with bkpt_gdbserver; what the core lacks is refused with the reason in the capture log ([hwtrace] ... not enabled: ...) and in the recording meta hardware_trace (refused[]), never silently. config echoes the block.
Probe selection: probes lists connected probes with serials. With one probe of the kind attached the serial may be omitted; with several, pass the serial or the capture refuses (no guessing). targets --filter STM32G474 lists probe-rs target names.
capture
viewalyzer-cli capture --config b.vacf --output run.vadb --duration 10 [--stream] [--stop-file f] [--keep-va] [--no-register]
--output: a.vadb(default:<app dir>/recordings/capture-<stamp>.vadb; the extension is adjusted and logged if something else is given), or a.vato keep only the byte-log.--duration: seconds of recording, counted from the moment the transport reportsRecording; omit it to run until Ctrl-C / SIGTERM or--stop-file(the file's existence ends the capture; content is ignored). Early stop printsStop requested at N s. Finalizing partial recording...and then the two contract lines: a partial recording is a normal recording.- A capture that ends with 0 events is an error (
empty_capture, exit 1) and leaves no file behind; look at the[capture]lines for the reason. - On
swo-tcp, an orderly server EOF ends and finalizes the capture immediately, even when--durationhas not elapsed. A missing listener reports onlyno SWO server at <address>; the source may be any simulator or debug server. --streamemits JSON lines on stderr while running, one event per line, its kind int:stream_initonce,stream_meta({"id":42,"name":"Sine Wave","display":"graph"}) as channels are discovered, thenstream_sample({"id":42,"t_us":1234,"value":98.0}) per point.idis the firmware's trace id.itm_text{port, t_us, text}: text the firmware wrote on an ITM stimulus port (SWO transports), as it arrives.swo_load{bytes_per_s, share_pct, overflows}: the SWO pin's load, one line per 500 ms window.pc_samples{total, sleep, pcs}: the on-chip PC samples since the previous line (sleepcounts samples taken while the core slept,pcscarries the sampled PCs).dwt_data{rows: [{cmp, t_us, v, size, w, pc}]}: DWT data-trace packets, one row per packet with the raw comparator valuev(cmpis the comparator,wtrue on a write,pcthe traced PC when the watch asked for it; the consumer types the value).exc{total, max_depth, exceptions: [{num, name, enter, exit, return, max_depth}]}: the exception-trace table, cumulative over the capture, sent at most every 200 ms.
- The byte-log (
.va) is written while the capture runs so a host crash loses nothing; it is removed after the.vadbis written unless--keep-va. --instrument-cmd "<cmd>"spawns an external instrument recorder (a power analyzer, a DAQ) for the capture window;--instrument-warmup-swaits before touching the target,--instrument-series <f>imports the instrument's series file after the capture, and--instrument-livemerges a stdout stream live. The sync recipe and the NDJSON series contract are indocs/EXTERNAL-SERIES.md.- The final stdout line is a JSON envelope:
{"schema_version":2,"recording_id":..., "path":..., "summary":{events, objects, bytes, lost_events, corrupt_bytes, duration_us, wall_s, os, cpu_hz, transport}}. - Free mode (no license): the capture is capped at 5 s, a 5 s cooldown follows (
cooldown_activewithretry_after_sif you come back too soon), and the recording keeps its first 10 task/ISR lanes. Each cap prints a[headless]line naming it;licenseshows the caps in force. Seedocs/LICENSING.md.
snapshot
viewalyzer-cli snapshot --config b.vacf --output crash.vadb
Reads the firmware's RAM ring through the probe without resetting the target (the core is halted for the dump and resumed). Needs a rambuf transport. Both ring shapes are recognised: the post-mortem ring (exact window, ring: "post-mortem", discarded_packets, frozen, wrapped) and the live drop-mode ring (single-shot dump, ring: "live-drop"). An empty ring or a window that parses to 0 events exits 1 (empty_snapshot / snapshot_failed). The envelope:
{ "schema_version": 2, "recording_id": "a82ee8857447", "path": "...\\crash.vadb",
"summary": { "ring": "live-drop", "events": 1895, "window_bytes": 16380, "packets": null,
"discarded_packets": 0, "wrapped": false, "frozen": false,
"wire_version": 1, "recorder_version": 256, "cpu_hz": 170000000,
"control_block": "0x2000E338", "lost_events": 0, "corrupt_bytes": 0 } }
reset --config b.vacf resets the target behind the probe (halt, reset, run) without capturing.
--elf <firmware.elf> on capture / snapshot / poll pins the control block from the image instead of scanning RAM (_VA_RAMBUF for rambuf transports, _SEGGER_RTT for RTT; logged as Control block at 0x... (resolved from ELF symbol ...)). Default RAM metadata capture requires the matching firmware ELF. Add --no-reset to attach to a running target. See RAM metadata attachment.
poll (memory polling, no firmware instrumentation)
viewalyzer-cli poll --config b.vacf --elf firmware.elf --symbols tick_counter,adc_value:u16 --poll-hz 100 --duration-s 10 [--stream] [--output p.vadb]
viewalyzer-cli symbols --elf firmware.elf [--filter motor] # the pollable symbol_legend
Samples the listed variables over the probe at a fixed rate. Each --symbols entry is name[:type], type one of u8|u16|u32|i8|i16|i32|f32 (default: the symbol's size, signed). Unknown symbols or types error out (symbol_not_found / bad_arguments) instead of polling the rest. Samples are poll_trace rows on a 1 MHz wall clock (meta.poll_clk_hz), served by query user-traces like any channel; --stream emits the same stream_init / stream_meta / stream_sample lines on stderr. --coalesce-gap <bytes> merges registers into ranged reads across up to N undeclared bytes (0 = only exactly-adjacent registers merge; passing more asserts the gap bytes are side-effect-free to read). The envelope: {"summary": {"span_seconds", "sample_count", "sample_loss_percent", "symbols_polled", "poll_hz"}}.
load, convert, export
viewalyzer-cli load run.vadb # summary JSON (events, lanes, channels, load time)
viewalyzer-cli load run.va --output run.vadb # convert a byte-log or raw wire dump into a .vadb
viewalyzer-cli load run.vadb --export events --format csv --out events.csv
viewalyzer-cli load run.vadb --export spans|tasks|channels|traces [--format json|csv] [--out file|-]
load accepts .vadb, .va byte-logs and raw wire dumps (no container header); all decode to the same recording. Any recording can be named by path or by the 12-hex recording_id from the index.
import <recording> --series <file.ndjson> merges an external instrument's series into an existing .vadb after the fact; the sync flags (--sync-name, --sync-tol-ms, --offset-s, ...) and the NDJSON contract are in docs/EXTERNAL-SERIES.md.
replay
viewalyzer-cli replay run.vadb [--perf] # flag form: viewalyzer --headless --replay run.vadb --perf
Re-parses a recording and prints one [metric] category,name,value,unit line per metric: the machine-parseable surface CI harnesses parse, same categories and names as the C++ app's --replay: info (fileSize KB, cpuFrequency, os), data (taskSlices, userTraces, syncEvents, timeSpan, corruptBytes, corruptRuns, seqSupported, lostEvents, seqGaps, priorityInversions, commFlows, cpuAnomalies), timer/work lanes, sync category counters, per-channel trace and per-lane task.slices / task.cpu rows, flow summaries, and result,status. --perf adds perf,<stage>,<ms>,ms timings for the same pipeline stages the GUI runs on open (parse, task stats, inversions, comm flows, CPU stats). Exit 0 when the recording carries any task slices or user traces, 1 otherwise. The C++ app's per-operation sync rows (give/take/…) are not emitted.
query
viewalyzer-cli query <verb> --recording <id|path> [--tier summary|bucketed|raw] [--budget low|med|high]
[--t-start-us N --t-end-us N] [--bucket-us N] [--kinds a,b] [--channels a,b]
All times in responses are microseconds since the recording start. Tasks are referenced by a compact code (T1, T2, ...) defined in the response's task_legend; data channels by C1, C2, ... in trace_legend. The draft 2020-12 schema catalog has a named schema for every verb at #/$defs/<verb>.
--tier summary(default) needs no window;bucketedneeds the window and--bucket-us;rawneeds the window.--budgetscales the caps (rows 200 / 1000 / 5000, buckets 64 / 256 / 1024, serialized size 12 / 48 / 200 KB). Exceeding a cap returnswindow_too_widewith asuggestion(a narrowert_end_us, a wider bucket, or a filter).
| VERB | SUMMARY | BUCKETED | RAW | |
|---|---|---|---|---|
summary | whole-recording scalars plus execution_ended_abnormally (boolean), ended_abnormally (fault/stack-trap time, task, and last event; otherwise null), execution_end (last event/task), recording integrity, and separate pre_sync acquisition counters | |||
timeline | per-lane cards: cpu_percent, slice_count, mean/p50/p95/p99/max slice, preemptions, observed period p50/p95, jitter, anomalies[] | per-lane CPU% per bucket | slices: [[task_code, t_start_us, dur_us], ...] | |
events | counts by_kind, top_objects (--kinds filters) | kind counts per bucket | individual events `{t_us, kind, start_end, code | obj, value_i, value_f, text}` |
user-traces | per channel {count, min, max, mean, last, rms, std_dev, rate_hz, min_at_us, max_at_us} (--channels by name or code) | per channel `[{min,max,mean,count} | null]` per bucket | samples: [[channel_code, t_us, value], ...] |
sql | --sql "SELECT ...": read-only, one statement, row cap by budget, BLOBs as "<blob N bytes>" | |||
inversions | scope: "mutexes_only"; every mutex contention includes waiter/holder names, priorities, and times. Semaphore ownership is absent from the wire format, so semaphore inversion is not inferred | |||
verdicts | unified verdicts[] from native CPU/fatal-end detectors and optional Trace Domain rules; detectors distinguishes missing descriptors, disabled descriptors, no rules, ready detectors, and true zero findings | |||
cpu | top-level CPU model fields (no cpu wrapper): busy_pct/idle_pct, sliding-window extremes, context_switches, preemptions, tasks[], and findings[]; --t-start-us/--t-end-us scope everything (window.scoped) | |||
timers | per kernel timer: measured, fires (confirmed: takes from the timer service task), predicted_fires (arm + duration arithmetic, never in the stats), arms/stops, phase-corrected mean/p99/max_lat_us, violations vs --threshold-us (500), 12-bin histogram, cb_mean_us; work[] k_work lanes (submits/schedules/cancels) | fire_records[] (+cause on violations), marks[] | ||
comms | paths[] (producer>via>consumer, kind, count, rate, median/p99/max latency, blocked) and resources[] (sends/receives/matched/still_pending/empty_receives/contentions/peak_pending); a window recomputes path stats, --bucket-us adds per-resource backlog | |||
series | compact curves use fields + values: [[t_us,value]]; --kind stack also exposes object rows as stack: [{t_us,total_stack_used_bytes}]. Other kinds: cpu-load, event-rate, heap, task-timing, interval | |||
slices, slice-details, events-all, user-traces-all | unbounded variants for consumers that manage their own payload size (--max-slices n, --kinds, --channels, optional window) |
Hardware-trace queries (no_hw_trace when the recording has none of the rows):
| VERB | PAYLOAD (DATA) |
|---|---|
profile [--elf f] | PC-sample hotspots: total_samples, sleep_samples, span_s, sample_rate_hz, hotspots[], source (dwt-swo | pcsr-poll), interval_cycles |
itm-console [--port N] | ports[] of {port, lines: [{t_us, text}], bytes} (lines split on newline, an unterminated tail is partial, lines_truncated when the cap cut a port); --port N (0..31) keeps only that port, an empty ports[] when it has no text; out of range is bad_arguments |
dwt-data | watches[] of {cmp, name, address, function, count, first: [{t_us, value, rw, pc?}]} (up to 256 samples each) |
dwt-exc | exceptions[] of {num, name, enter, exit, return, max_depth} plus events[] {t_us, num, func} (up to 4096) |
dwt-counters | counters.{cpi,exc,sleep,lsu,fold,cyc} = {wraps, cycles, rate_per_s}, span_s, cpu_load_pct (from the sleep counter, null without it) |
swo-load | {bytes, seconds, bytes_per_s, swo_hz, share_pct, overflows} |
Not available (they error naming the reason): irq (no_hw_trace), etm (etm_not_present), target-caps.
hwtrace (dry run)
viewalyzer-cli hwtrace --dry-run --arch v7m|v8m|v6m --caps '{"numcomp":4,"notrcpkt":0,"nocyccnt":0,"noprfcnt":0,"itm":1,"tpiu":1}'
--cpu-clock-hz N --swo-freq-hz N --itm-port P [--no-init-swo] --hardware-trace <json|@file> [flat --dwt* flags]
Prints the ordered register image the capture would program on that core: {schema, arch, cpu_hz, swo_hz, writes: [{reg, addr, value}], refused: [{feature, reason}], applied}. Exit 2 with {"error": ...} on a configuration error. bkpt_gdbserver --dry-run prints the same image for the same inputs.
Fingerprint baselines: query fingerprint / query compare
Golden-run regression testing. A fingerprint is a small, git-committable .vafp.json distilled from a recording: selected metrics with per-metric tolerances plus capture provenance (OS, CPU clock, capture source). Counts are normalised to per-second rates so runs of different length compare fairly.
viewalyzer-cli query fingerprint --recording good.vadb --out app.vafp.json
viewalyzer-cli query fingerprint --recording good1.vadb --runs good2.vadb,good3.vadb --out app.vafp.json # envelope learning
viewalyzer-cli query compare --recording candidate.vadb --baseline app.vafp.json # exit 0 pass/warn, 2 regression, 1 error
compare accepts baselines in either dialect: this app's ("format": "vafp") or the C++ app's ("format": "va-fingerprint", sections as {match, metrics, items}); the latter is normalised on load, so envelopes seeded by either app gate either app's recordings. Four summary metrics are defined differently by the two engines and will disagree across dialects until reconciled: context_switches_hz (C++ counts every slice, this engine counts task_switch events), events_per_second (C++ counts slices+traces+syncs, this engine counts raw store events), total_tasks (C++ includes Fn: lanes, this engine counts only real tasks), and cpu_percent on Fn: items (C++ reports 0 for function lanes).
| FLAG | MEANING |
|---|---|
--runs a,b | extra recordings merged into the envelope (value = mean, min/max = observed range) |
--sections summary,tasks,traces,timers,comms,health | restrict the sections (default: all) |
--tolerance-pct n | default relative FAIL threshold per metric (25) |
--warn-pct n | default relative WARN threshold (80 % of the fail threshold) |
--out f.vafp.json | write the fingerprint (pretty JSON) |
--baseline f | a .vafp.json, or any recording (fingerprinted on the fly) |
Sections: summary (cpu_load_percent, events_per_second, context_switches_hz, preemptions_hz, exact total_tasks / trace_channels, heap scalars when present), tasks (per lane by name: cpu_percent, run_hz, avg_run_us, avg_period_us, max_jitter_us (advisory), preemption_hz, exact priority, stack, sync-op rates, failed_ops_hz), traces (per channel: rate_hz, mean_value, rms, std_dev, advisory min_value/max_value), timers (fires_hz, violations, lateness stats; unmeasured lanes pin predicted_fires_hz only; work:<label> items), comms (per path: rate_hz, median_us, p99_us, blocked_hz; resource:<name>: peak_pending, still_pending, empty_receives_hz), health (corrupt_bytes, corrupt_runs, lost_events, seq_gaps: baselines are normally 0, so any loss fails).
Compare: a metric passes inside the baseline's [min, max] envelope or within its warn band (abs_warn = value x warn_pct), warns up to the fail band (abs_tol), fails beyond; exact metrics must match. A baseline item missing from the run is missing (fail); an item the baseline never saw is new (warn). Provenance mismatches never fail; they appear in provenance_warnings. Results are sorted fail, missing, warn/new, pass, with counts and the overall verdict. The file is hand-editable: change any metric's tol_pct / warn_pct, or set abs_tol / abs_warn directly; delete items you do not want pinned.
Each metric result uses current for the candidate recording passed via --recording and baseline for the fingerprint/envelope value. Exit codes are 0 for pass or warn, 2 for a regression verdict, and 1 for a command, input, or I/O error.
The recording index
recordings lists {recording_id, path, schema_name, duration_us, size_bytes, created_utc} for every registered file that still exists. The id is a stable hash of (absolute path, mtime). --delete-recording <id> removes the entry and the file; --delete-all-recordings empties the index. The index lives at <app dir>/recordings/index.json (%APPDATA%\ViewAlyzer-GPUI, ~/Library/Application Support/ViewAlyzer-GPUI, ~/.config/ViewAlyzer-GPUI). Hosts that manage their own files can pass paths and --no-register.
Inside a .vadb
One SQLite file per recording, the schema the GUI and the Python SDK read: meta (key/value: format, version, capture_source, capture_utc, va_cpu_hz, va_os, ...), va_objects (id, ns, va_id, name, extra), va_events (id, t_cycles, kind, start_end, obj, obj2, value_i, value_f, text; t_cycles in CPU cycles, seconds = t_cycles / va_cpu_hz; text is payload text and does not duplicate an object's name—join va_events.obj to va_objects.id), health (stage='recorder': corrupt_bytes, corrupt_runs, lost_events, seq_gaps, pre_sync_corrupt_bytes, pre_sync_corrupt_runs, pre_sync_lost_events, pre_sync_seq_gaps, main_trace_bytes), raw_log (the byte-log, section='va_file'), va_summary, va_task_stats, va_trace_stats, va_timer_stats, va_work_stats, va_comm_paths, va_comm_resources, and va_signal_blocks (full-rate waveforms, present only when a file has any).
Hardware trace rides va_events (no schema change, meta.version stays 1): kinds dwt_pc (ns dwt va_id 0x2000, value_i = PC or NULL when asleep), dwt_exc (va_id 0x1000 + exception number, value_i = 1 enter / 2 exit / 3 return), dwt_data (va_id = comparator 0-3 named after the watch, value_i = value, obj2 = size, text = w \| r plus pc=0x... when traced), dwt_data_pc (PC-only comparators), dwt_counters (va_id 0x2010 + counter index cpi/exc/sleep/lsu/fold/cyc, value_i = cycles per wrap), itm_text (ns itm, va_id = port, text). Meta keys hardware_trace (the applied image, found registers and swo_load), pc_sample_source, pc_sample_interval_cycles, va_itmhw_bytes, va_itmhw_overflows.
Utilities
| COMMAND | PURPOSE |
|---|---|
version | {schema_version, app, version, core: "rust", edition, license: {type, tier, licensed, partner?, expires?}, transports[]} |
license [get|activate <key>|validate|deactivate|install <file>] | local license state, effective caps and cooldown_remaining_s (get, never online); the lifecycle verbs are the only online calls; install verifies and copies an OEM file. docs/LICENSING.md has the envelopes |
probes | {probes: [{type, serial, description, vid, pid}]} |
ports | {ports: ["COM7", ...]} |
targets [--filter s] | probe-rs target names |
symbols --elf f [--filter s] | data symbols of a firmware image (symbol_legend) |
callgraph --elf f [--root fn] [--depth n] [--direction out|in|both] [--entries] | static call graph decoded from the image's code (bl and tail b.w, no toolchain needed): nodes[] with BFS layout, edges[], decode stats; --entries lists entry points (main, reset, vector-table handlers) |
memory --elf f [--map f] | flash/RAM usage by section; with the linker MAP: region capacities (memory_regions[] with used/free/percent), MAP-placed sections[], and discarded_sections[] (map_not_found if the file is missing) |
reset | reset the target behind the configured probe |
doctor | probes per kind, serial ports, recordings dir, target registry, license, caps_in_effect, caps_source (free, license, or none), and effective_caps (also repeated on the license check row); always exits 0 |
config | effective connection config for the given --config / flags |
help | flag reference |
Validate a custom Trace Domain
viewalyzer-cli parse-trace-domain my-domain.vadomain --pretty
viewalyzer --headless --parse-trace-domain my-domain.vadomain --pretty
viewalyzer-cli parse-trace-domain --schema
This offline check uses the same parser as the applications. No SDK, probe or target connection is required. Pass one or more filenames; JSON output includes schema_version, domain_api, valid, and a files array with per-file results. Errors name the precise path, JSON Pointer pointer and message; syntax errors include line and column. Exit status is 0 for valid files, 1 for rejected content, or 2 for unreadable files or invalid arguments. Batch validation checks every file; status 2 takes precedence over 1. The --schema form takes no files and returns the structural schema in the response's schema property. Complete validation also checks semantic constraints and references.
See Creating custom Trace Domains for the full format, steps, examples and the optional Python SDK wrapper.