From 1abbe31787afb9c8b6b9fcf93da3fd48bea9cec3 Mon Sep 17 00:00:00 2001 From: Firas Shaari Date: Tue, 26 May 2026 18:28:38 -0400 Subject: [PATCH] state: bridge SSID tx_failed/tx_retries gap on mt76 and ath11k mt76 (mt7621/mt7915) and ath11k (QSDK) drivers do not propagate per-STA TX status to mac80211. As a result, ssid.counters.tx_failed and ssid.counters.tx_retries always report 0 even under heavy traffic. Each driver tracks comparable RF-only retry/fail counters in its own debugfs: mt76: /sys/kernel/debug/ieee80211//mt76/tx_stats (BA miss count) ath11k: /sys/kernel/debug/ieee80211//ath11k/htt_stats (tx_xretry) Read whichever path the phy's driver exposes and project the failure count onto each VAP, weighted by tx_packets share. The existing generate_deltas pipeline populates ssid.delta_counters automatically. Verified on yuncore_ax820 (mt76) and yuncore_fap655 (ath11k) with live Kafka showing nonzero tx_failed/tx_retries. Signed-off-by: Firas Shaari --- system/state/wifi.uc | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/system/state/wifi.uc b/system/state/wifi.uc index 5adb750..d41237b 100644 --- a/system/state/wifi.uc +++ b/system/state/wifi.uc @@ -111,6 +111,60 @@ export function collect_wifi_radios(state) { push(radio.survey, v); } delete radio.in_use; + + /* Bridge SSID tx_failed/tx_retries gap on hardware that doesn't + * propagate per-STA TX status to mac80211 (mt76 on mt7621/mt7915, + * ath11k on QSDK). Each driver tracks comparable RF-only retry/fail + * counters in its own debugfs: + * mt76: .../mt76/tx_stats (BA miss count) + * ath11k: .../ath11k/htt_stats (tx_xretry via type 1) + * Project the phy-aggregate count onto each VAP, weighted by + * tx_packets share. + */ + if (length(data.interfaces) && data.interfaces[0].ifname) { + let ifname = data.interfaces[0].ifname; + let phyname_raw = fs.readfile('/sys/class/net/' + ifname + '/phy80211/name'); + let phyname = phyname_raw ? trim(phyname_raw) : null; + let phy_fail = 0; + if (phyname) { + let mt76_raw = fs.readfile('/sys/kernel/debug/ieee80211/' + phyname + '/mt76/tx_stats'); + if (mt76_raw) { + let m = match(mt76_raw, /BA miss count: ([0-9]+)/); + if (m) phy_fail = +m[1]; + } else { + let ath_type = fs.open('/sys/kernel/debug/ieee80211/' + phyname + '/ath11k/htt_stats_type', 'w'); + if (ath_type) { + ath_type.write('1\n'); + ath_type.close(); + sleep(100); + let htt_raw = fs.readfile('/sys/kernel/debug/ieee80211/' + phyname + '/ath11k/htt_stats'); + if (htt_raw) { + let m = match(htt_raw, /tx_xretry = ([0-9]+)/); + if (m) phy_fail = +m[1]; + } + } + } + } + + if (phy_fail > 0) { + let total_tx = 0; + for (let v in data.interfaces) { + if (global.ports && global.ports[v.ifname] && global.ports[v.ifname].counters) + total_tx += +global.ports[v.ifname].counters.tx_packets || 0; + } + for (let v in data.interfaces) { + if (!global.ports || !global.ports[v.ifname] || !global.ports[v.ifname].counters) + continue; + let vap_tx = +global.ports[v.ifname].counters.tx_packets || 0; + let attributed = total_tx > 0 + ? int(phy_fail * vap_tx / total_tx) + : int(phy_fail / length(data.interfaces)); + global.ports[v.ifname].counters.tx_failed = (+global.ports[v.ifname].counters.tx_failed || 0) + attributed; + global.ports[v.ifname].counters.tx_retries = (+global.ports[v.ifname].counters.tx_retries || 0) + attributed; + } + } + } + push(radios, radio); }