Skip to content

Record each node's radio counters in every experiment cell - #746

Open
NomDeTom wants to merge 2 commits into
MeshBench:mainfrom
NomDeTom:experiment-chip-counters
Open

NomDeTom wants to merge 2 commits into
MeshBench:mainfrom
NomDeTom:experiment-chip-counters

Conversation

@NomDeTom

@NomDeTom NomDeTom commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Builds on #745: this branch contains #745's commit, and the change here is the last commit (experiment: every cell records what each node's chip saw). It merges after #745.

What this changes, and why

Each cell in an experiment gains chip (ChipStats in internal/app/session/experiment/chipstats.go):

  • summed across the cell's running nodes: irq_reads, busy_reads, busy_ms, spurious, and irq_suppressed with suppressed_reporting (how many nodes reported a suppressed count);
  • per node (nodes): name, kind, the same counters, whether the suppressed count was reported, irq_mask, sf and bandwidth_hz.

The counters are read in runArm before the engine closes, from the last radio stats record each node's bridge received. They reach experiment.export. experiment.results is unchanged. experiment.export's description names the field, and docs/verb-reference.md is regenerated from it.

Without these, an experiment row carries delivery totals only, so two arms whose firmware read different radio state but transmitted identically produce identical rows.

Manual: MeshBench/docs#40.

Verification

  • gofmt -l internal/: empty.
  • go vet ./internal/app/session/experiment/: clean.
  • go test ./internal/app/session/experiment/ ./internal/: pass.
  • golangci-lint v2.12.2 on internal/app/session/experiment: no findings in changed files.
  • tools/verbdoc/verbdoc.py --check: current after regeneration.
  • New tests in chipstats_test.go: sums and per-node rows; a node without a suppressed count is neither added to the total nor counted as reporting; the export row carries chip.
  • End to end (local headless runs, native MeshCore 1.16.0 and 1.17.0 builds with bridge: report what the chip's IrqMask kept out meshcore-native#26, scotland-strict, five seeds): every cell's chip listed 159 nodes. irq_mask read 0x0272 for 1.16 and 0x0276 for 1.17; sf 8 and bandwidth_hz 62500 throughout, except one node in one seed (see below); spurious 0. The delivery totals of the 1.16 and 1.17 arms were identical per seed, while busy_ms and irq_suppressed differed between them.
  • One unexplained reading: in seed 4417, node AngusOutlaw1 reported bandwidth_hz 10420 at the end of the cell, in every arm. It is recorded here as observed.

Checklist

  • One independent change. If it is two, it is two pull requests. (Depends on Read the chip's IrqMask-suppressed count from native radio stats #745.)
  • A new package updates the layout map in CLAUDE.md in this commit. (No new package.)
  • A new dependency is justified here, in one line. (None.)
  • Files under 500 lines; new panels and widgets are one type per file. (cell.go is 490 lines; the collection is in its own file.)
  • A new panel joins auditTargets; a new state is reachable by flag. (No panel.)
  • docs/shortcomings.md still tells the truth after this change.
  • Every claim here is verified, not assumed — no invented verb, flag, path or result.
  • A behaviour change is covered by a test that fails without it.
  • Nothing staged that was not written or asked for — no build artifacts, nothing under security-audit/.

A chip that applies the firmware's IrqMask counts the events it had to raise
and was not allowed to. A native node can append that count to its radio
stats record as bytes 43-46; the bridge reads it when the record is at least
47 bytes long. node.stats answers it as irq_suppressed, and the Python
client's NodeStat carries it.

A record shorter than 47 bytes comes from a chip that does not count, and
irq_suppressed is then absent (None in the Python client) rather than zero.
Each cell's result gains `chip`: the radio counters every running node
reported at the end of the cell (IRQ register reads, reads that found a
detection flag set, milliseconds with a detection flag set, injected
spurious detections, and events the IrqMask suppressed), summed across the
cell and kept per node with the node's IRQ mask, spreading factor and
bandwidth. They are read before the engine closes, from the last record
each node's bridge received.

The suppressed total counts only nodes whose chip reports it;
`suppressed_reporting` says how many did, so a total of zero is not read as
a count of zero from chips that do not keep one.

The experiment.export description names the field, and the generated
verb reference is regenerated from it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant