BKPT LabsDOCS/VIEWALYZER CLI/VIEWALYZER CLI (HEADLESS) VIEWALYZER · HEADLESS CLI
VIEWALYZER · CLI & AUTOMATION

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 10viewalyzer --headless --config b.vacf --output run.vadb --duration 10
viewalyzer-cli probesviewalyzer --headless --list-probes
viewalyzer-cli snapshot --config b.vacf --output crash.vadbviewalyzer --headless --snapshot --config b.vacf --output crash.vadb
viewalyzer-cli query timeline --recording <id> --tier summaryviewalyzer --headless --query timeline --recording <id> --tier summary
viewalyzer-cli replay run.vadb --perfviewalyzer --headless --replay run.vadb --perf
viewalyzer-cli versionviewalyzer --headless --version
viewalyzer-cli recordingsviewalyzer --headless --list-recordings
viewalyzer-cli memory --elf f [--map f]viewalyzer --headless --analyze-memory --elf f [--map f]
viewalyzer-cli symbols --elf fviewalyzer --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
stdoutquery / 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 code0 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 NDWT 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,cycevent counters (cyc is refused while PC sampling is on: both use POSTCNT)
--itm-ports, --itm-privilege, --itm-timestamps off|1|4|16|64stimulus 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|swopoll 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 .va to keep only the byte-log.
  • --duration: seconds of recording, counted from the moment the transport reports Recording; omit it to run until Ctrl-C / SIGTERM or --stop-file (the file's existence ends the capture; content is ignored). Early stop prints Stop 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 --duration has not elapsed. A missing listener reports only no SWO server at <address>; the source may be any simulator or debug server.
  • --stream emits JSON lines on stderr while running, one event per line, its kind in t:
    • stream_init once, stream_meta ({"id":42,"name":"Sine Wave","display":"graph"}) as channels are discovered, then stream_sample ({"id":42,"t_us":1234,"value":98.0}) per point. id is 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 (sleep counts samples taken while the core slept, pcs carries 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 value v (cmp is the comparator, w true on a write, pc the 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 .vadb is written unless --keep-va.
  • --instrument-cmd "<cmd>" spawns an external instrument recorder (a power analyzer, a DAQ) for the capture window; --instrument-warmup-s waits before touching the target, --instrument-series <f> imports the instrument's series file after the capture, and --instrument-live merges a stdout stream live. The sync recipe and the NDJSON series contract are in docs/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_active with retry_after_s if you come back too soon), and the recording keeps its first 10 task/ISR lanes. Each cap prints a [headless] line naming it; license shows the caps in force. See docs/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; bucketed needs the window and --bucket-us; raw needs the window.
  • --budget scales the caps (rows 200 / 1000 / 5000, buckets 64 / 256 / 1024, serialized size 12 / 48 / 200 KB). Exceeding a cap returns window_too_wide with a suggestion (a narrower t_end_us, a wider bucket, or a filter).
VERB SUMMARY BUCKETED RAW
summarywhole-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
timelineper-lane cards: cpu_percent, slice_count, mean/p50/p95/p99/max slice, preemptions, observed period p50/p95, jitter, anomalies[]per-lane CPU% per bucketslices: [[task_code, t_start_us, dur_us], ...]
eventscounts by_kind, top_objects (--kinds filters)kind counts per bucketindividual events `{t_us, kind, start_end, codeobj, value_i, value_f, text}`
user-tracesper 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 bucketsamples: [[channel_code, t_us, value], ...]
sql--sql "SELECT ...": read-only, one statement, row cap by budget, BLOBs as "<blob N bytes>"
inversionsscope: "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
verdictsunified 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
cputop-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)
timersper 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[]
commspaths[] (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
seriescompact 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-allunbounded 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-datawatches[] of {cmp, name, address, function, count, first: [{t_us, value, rw, pc?}]} (up to 256 samples each)
dwt-excexceptions[] of {num, name, enter, exit, return, max_depth} plus events[] {t_us, num, func} (up to 4096)
dwt-counterscounters.{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,bextra recordings merged into the envelope (value = mean, min/max = observed range)
--sections summary,tasks,traces,timers,comms,healthrestrict the sections (default: all)
--tolerance-pct ndefault relative FAIL threshold per metric (25)
--warn-pct ndefault relative WARN threshold (80 % of the fail threshold)
--out f.vafp.jsonwrite the fingerprint (pretty JSON)
--baseline fa .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)
resetreset the target behind the configured probe
doctorprobes 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
configeffective connection config for the given --config / flags
helpflag 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.