Every scenario run produces a report: its events, its assertion verdicts and every telemetry sample taken during the run. This page shows how to get reports off the board and read them. The report format itself is described in Assertions and Reports.
Getting reports#
satlinkctl runs --limit 20 # recent runs and verdicts
curl -s $SATLINK_API/api/v1/reports | jq '.[0:5]' # summaries
curl -s $SATLINK_API/api/v1/reports/<run-id> > run.json # one full report
curl -s $SATLINK_API/api/v1/scenario-runs/<run-id>/events # its events onlyIn the Web Console, Library → Reports lists runs; each report has its own page with the events, the assertions and the telemetry series, and an Export JSON button.
Reading a report#
Check these, in this order, before reading any verdict:
-
Provenance.
sourcemust bepl:stubmeans no bitstream answered and nothing was measured.Shell jq '{status, verified, aborted, source, sample_interval_ms}' run.json -
Traffic. Did anything cross?
tx_framesis cumulative, so look at its movement:Shell jq '[.metric_samples[].tx_frames | select(. != null)] | {first: .[0], last: .[-1]}' run.jsonA report showing
lock: falsefor fifty samples cannot be read at all without this: "the link is broken" and "nothing was transmitted" look the same otherwise. -
Did the timeline do what the file says? Look for staged, refused or unavailable events:
Shell jq -r '.events[] | select(.kind | test("staged|refused|unavailable")) | "\(.elapsed_ms)ms \(.kind): \(.description)"' run.json -
The assertions, with their measured values and windows:
Shell jq -r '.assertions[] | "\(if .passed then "PASS" else "FAIL" end) \(if .verified then "" else "(NOT VERIFIED) " end)\(.name): \(.message)"' run.json
Useful extractions#
Frames delimited against frames sent, per sample:
jq -r '.metric_samples[] | [.elapsed_ms, .tx_frames, .rx_frames, .rx_frame_errors, .agc_gain, .lock, .carrier_lock] | @tsv' run.jsonFraction of samples locked (timing and carrier) over the whole run:
jq '[.metric_samples[]] | {n: length,
timing: (map(select(.lock)) | length),
carrier: (map(select(.carrier_lock)) | length)}' run.jsonA watched register over time (here the deframer's matched rotation, bits [4:3]):
jq -r '.metric_samples[] | [.elapsed_ms, (((.watched["rx_demod.deframer.status"] // 0) / 8 | floor) % 4)] | @tsv' run.jsonThe channel emulator's active state around a step, to prove an impairment reached the link:
jq -r '.metric_samples[] | select(.elapsed_ms > 29000 and .elapsed_ms < 36000) | [.elapsed_ms, .chan_ctrl, .lock] | @tsv' run.jsonSample and event times are elapsed_ms since the start of the run and are reliable.
started_at/ended_at come from the board's clock, which reads 1970 unless it has been set.
Comparing runs#
- Fix the offered load. Give traffic steps a
countso two runs send the same number of packets; otherwise you compare the throughput each run happened to get. - Repeat before concluding. This bench has boot-to-boot variability: the same configuration can behave differently on two boots. Repeat a measurement and its control a few times, across reboots, before attributing a difference.
- Keep the controls in the same run. Running the reference condition first and last licenses reading the middle against them.
- Beware of beautiful single results. A structured, convincing result from one run has turned out to be an acquisition transient more than once.
Frames and link margin#
Received data is also available outside reports:
GET /api/v1/frames: a ring of recent RX blocks (boot-relative times, one entry per DMA block);ws://<board>:8080/ws/frames: the live receive stream, per packet in slot framing;- the ZeroMQ
frame.rxtopic, whosemarginfield carries soft marker matches and Reed-Solomon corrections per block, and whosequalityfield carries the FECF verdict when transfer frames are configured (see ZeroMQ Link Service).
Campaign results#
satlinkctl campaign status <id> (or GET /api/v1/campaign-runs/{id}) gives each step's attempts
and verdicts, and the scenario run id of every scenario step; fetch those reports as above. The
campaign state itself persists across reboots; the scenario reports it points to do not.