Skip to content

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

Merged
A13xB0 merged 5 commits into
MeshBench:mainfrom
NomDeTom:experiment-chip-counters
Oct 14, 2026
Merged

A13xB0 merged 5 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.
@A13xB0

A13xB0 commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

got some time, so hijacking to get things moving

@A13xB0

A13xB0 commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

I've pushed two commits to the branch to get CI green, merging main and regenerating docs/verbs.json...

I am going to make the following change quickly:

The chip counters will be measured from the cell's baseline rather than from boot.

runArm marks baseline and calls eng.ResetSensitivity() after the adverts and the 6 s settle, so the cell's delivery totals don't count the boot chatter. chipStats reads the bridge's totals from boot, so irq_reads, busy_reads, busy_ms, spurious and irq_suppressed currently include the boot and advert phase, and tx, rx and delivered don't. I'll snapshot each node's stats at baseline and report the end value minus the snapshot. irq_mask, sf and bandwidth_hz stay as end-of-cell readings, since they're state rather than counts. I'll add a test that counts from before the baseline don't reach the cell.

This may change the comparison in the description. Identical delivery totals alongside a different busy_ms could have come partly from the boot phase, so it's worth rerunning the 1.16 vs 1.17 seeds once this lands.

Separately, AngusOutlaw1 reporting bandwidth_hz 10420 is the SX126x's 10.4 kHz setting, a valid value rather than a decode error. It appears in one seed but in every arm with that seed, so I'll open an issue for it rather than hold this PR.
(#748)

The cell's delivery totals and margin counters start after the adverts
have settled; the chip counters ran from boot, so they carried the mesh
coming up while the totals beside them did not. Each node's radio record
is now read at the same baseline and the counters reported as the
difference. The mask and modem settings stay as the chip held them at
the end, and a counter that went backwards is a node that restarted, so
it is taken whole rather than wrapped.
@A13xB0
A13xB0 merged commit 00c9252 into MeshBench:main Oct 14, 2026
9 checks passed
This was referenced Oct 14, 2026
@NomDeTom
NomDeTom deleted the experiment-chip-counters branch October 15, 2026 17:15
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.

2 participants