Skip to content

fix(firmware): the node log's edge frame counters have always reported zero - #1931

Open
clonea1 wants to merge 2 commits into
ruvnet:mainfrom
clonea1:contrib/node-log-edge-counters
Open

clonea1 wants to merge 2 commits into
ruvnet:mainfrom
clonea1:contrib/node-log-edge-counters

Conversation

@clonea1

@clonea1 clonea1 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

node_log_periodic_t has declared frames_processed and frames_rejected
since the on-node log shipped, and node_log_read.py has always rendered
both. Nothing ever assigned them. fill_health() memsets the struct and fills
20 of its 25 fields; these two are not among them, so every periodic record
on every node reports 0/0
— the memset, not a measurement.

Why these two specifically matter

EDGE_MAX_SUBCARRIERS is target-conditional — edge_processing.h picks 256
on CONFIG_SOC_WIFI_HE_SUPPORT and 128 otherwise — and process_frame()
opens with n_subcarriers > EDGE_MAX_SUBCARRIERS -> return. A C6 that
resolves that guard to 128 rejects every HE20 frame while the Edge DSP
task still starts and logs its banner.

From outside, "the edge stage is idle" and "the edge stage is rejecting 100%
of frames" look identical. The log exists to diagnose a node you cannot
reach, and on exactly that question it was returning a plausible constant
zero.

The change

Half the data was already here — s_frame_count exists and is incremented on
the accept path. Only the reject path was uncounted. So:

  • add s_frame_rejected beside s_frame_count
  • increment it in the guard rather than returning silently
  • reset it alongside s_frame_count in edge_processing_init()
  • expose both via edge_processing_get_frames_processed/rejected()
  • assign them in fill_health()

Three files, ~15 lines net.

Measured

Found by reading a real node's log: frames_processed=0 across 64 records at
5.5 days uptime with CSI flowing at 19 fps. That reads as a dead edge
pipeline and was very nearly reported as one — it was the logging.

After the change, rolled across nine ESP32-C6 nodes:

node fps processed rejected heap rssi
0 18.0 2007 0 188K -67
1 23.0 1527 0 187K -70
2 32.0 1538 0 188K -51
3 27.0 3447 0 187K -66
4 26.0 1045 0 187K -60
5 23.0 565 0 188K -72
6 29.0 571 0 188K -72
7 27.0 87 0 184K -75
8 30.0 90 0 188K -67

Non-zero processed with zero rejected on all nine is also the first runtime
evidence that the guard is sized correctly on C6 — at 128 the inequality
inverts. That was previously arguable only from include order.

Verification

Builds clean for esp32c6 on this base, and both accessors are present in
the linked ELF (riscv32-esp-elf-nm). The only main/ diagnostic is the
pre-existing unused-variable warning at mmwave_sensor.c:246.

Not included

Five more fields in the same record are unpopulated for the same reason —
seq_drop, tx_early_drop, tx_rate_skip, leader_id,
mesh_staleness_ms, mesh_seq. Each needs a new accessor rather than an
existing one, so they are a separate change. The underlying issue is that the
struct, fill_health() and node_log_read.py are three places that must
agree and nothing checks that they do — a test asserting every field is
written would stop the next one landing the same way. Happy to add that here
if you would prefer it in one go.

🤖 Generated with claude-flow

…fields that were always zero

`node_log_periodic_t` has declared `frames_processed` and `frames_rejected`
since the on-node log shipped, and `node_log_read.py` has always rendered
both. Nothing ever assigned them. `fill_health()` memsets the struct and fills
20 of its 25 fields; these two are not among them, so every periodic record on
every node reports 0/0 -- the memset, not a measurement.

That is the most misleading pair of values this record can carry.
`EDGE_MAX_SUBCARRIERS` is target-conditional: `edge_processing.h` picks 256 on
`CONFIG_SOC_WIFI_HE_SUPPORT` and 128 otherwise, and `process_frame()` opens
with `n_subcarriers > EDGE_MAX_SUBCARRIERS -> return`. A C6 that resolves the
guard to 128 therefore rejects every HE20 frame while the Edge DSP task still
starts and logs its banner. From outside, an edge stage that is idle and one
that is rejecting 100% of frames look identical -- and the log whose entire
purpose is diagnosing a node you cannot reach answered that question with a
plausible constant zero.

Half the data was already here: `s_frame_count` exists and is incremented on
the accept path. Only the reject path was uncounted, so this adds
`s_frame_rejected` next to it, increments it in the guard instead of returning
silently, resets it alongside `s_frame_count` in init, exposes both through
accessors, and wires them into `fill_health()`.

Measured, not argued. Found by reading a real node's log: `frames_processed=0`
across 64 records at 5.5 days uptime with CSI flowing at 19 fps, which reads
as a dead edge pipeline and was very nearly reported as one. After this change,
rolled across nine ESP32-C6 nodes:

```
node  fps  processed  rejected   heap   rssi
0    18.0       2007         0   188K   -67
1    23.0       1527         0   187K   -70
2    32.0       1538         0   188K   -51
3    27.0       3447         0   187K   -66
4    26.0       1045         0   187K   -60
5    23.0        565         0   188K   -72
6    29.0        571         0   188K   -72
7    27.0         87         0   184K   -75
8    30.0         90         0   188K   -67
```

Non-zero processed with zero rejected on all nine is also the first runtime
evidence that the guard is sized correctly on C6. At 128 the inequality
inverts: every frame rejected, none processed. That was previously arguable
only from include order.

Builds clean for esp32c6 on this base; both accessors are present in the
linked ELF. The only `main/` diagnostic is the pre-existing unused-variable
warning in `mmwave_sensor.c:246`.

Co-Authored-By: claude-flow <ruv@ruv.net>
@oga35767-eng

Copy link
Copy Markdown

Ty

@oga35767-eng

Copy link
Copy Markdown

Yy

… KiB

Unblocks the "Verify binary size budget" step of Build firmware
(esp32s3 / 8mb) -- this branch's head already carries the image growth
from 130afab but was still checked against the old 1152 KiB limit.
Does not address this branch's separate RUSTSEC-2026-0285 (rustls)
failure on Rust Dependency Audit.

Co-Authored-By: claude-flow <ruv@ruv.net>

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.

2 participants