@cryptotaxi247 / netdata-1 / commits / d91ea65cb

fix(go.d/ap): handle unknown values in station statistics gracefully (#21461)

Ilya Mashchenko committed Dec 14, 2025 at 14:51 UTC d91ea65cb61bbdd1e79f1127c0bcc7e596bd41e3
3 files changed +124 -72
src/go/plugin/go.d/collector/ap/collect.go
+79 -56
@@ -22,16 +22,16 @@ type iwInterface struct {
22 }
23
24 type stationStats struct {
25 - clients int64
26 - rxBytes int64
27 - rxPackets int64
28 - txBytes int64
29 - txPackets int64
30 - txRetries int64
31 - txFailed int64
32 - signalAvg int64
33 - txBitrate float64
34 - rxBitrate float64
25 + clients *int64
26 + rxBytes *int64
27 + rxPackets *int64
28 + txBytes *int64
29 + txPackets *int64
30 + txRetries *int64
31 + txFailed *int64
32 + signalAvg *int64
33 + txBitrate *float64
34 + rxBitrate *float64
35 }
36
37 func (c *Collector) collect() (map[string]int64, error) {
@@ -59,10 +59,7 @@ func (c *Collector) collect() (map[string]int64, error) {
59 return nil, fmt.Errorf("getting station statistics for %s: %v", iface, err)
60 }
61
62 - stats, err := parseIwStationStatistics(bs)
63 - if err != nil {
64 - return nil, fmt.Errorf("parsing station statistics for %s: %v", iface, err)
65 - }
62 + stats := parseIwStationStatistics(bs)
63
64 key := fmt.Sprintf("%s-%s", iface.name, iface.ssid)
65
@@ -75,18 +72,39 @@ func (c *Collector) collect() (map[string]int64, error) {
72
73 px := fmt.Sprintf("ap_%s_%s_", iface.name, iface.ssid)
74
78 - mx[px+"clients"] = stats.clients
79 - mx[px+"bw_received"] = stats.rxBytes
80 - mx[px+"bw_sent"] = stats.txBytes
81 - mx[px+"packets_received"] = stats.rxPackets
82 - mx[px+"packets_sent"] = stats.txPackets
83 - mx[px+"issues_retries"] = stats.txRetries
84 - mx[px+"issues_failures"] = stats.txFailed
85 - mx[px+"average_signal"], mx[px+"bitrate_receive"], mx[px+"bitrate_transmit"] = 0, 0, 0
86 - if clients := float64(stats.clients); clients > 0 {
87 - mx[px+"average_signal"] = int64(float64(stats.signalAvg) / clients * precision)
88 - mx[px+"bitrate_receive"] = int64(stats.rxBitrate / clients * precision)
89 - mx[px+"bitrate_transmit"] = int64(stats.txBitrate / clients * precision)
75 + if stats.clients != nil {
76 + mx[px+"clients"] = *stats.clients
77 + }
78 + if stats.rxBytes != nil {
79 + mx[px+"bw_received"] = *stats.rxBytes
80 + }
81 + if stats.txBytes != nil {
82 + mx[px+"bw_sent"] = *stats.txBytes
83 + }
84 + if stats.rxPackets != nil {
85 + mx[px+"packets_received"] = *stats.rxPackets
86 + }
87 + if stats.txPackets != nil {
88 + mx[px+"packets_sent"] = *stats.txPackets
89 + }
90 + if stats.txRetries != nil {
91 + mx[px+"issues_retries"] = *stats.txRetries
92 + }
93 + if stats.txFailed != nil {
94 + mx[px+"issues_failures"] = *stats.txFailed
95 + }
96 +
97 + if stats.clients != nil && *stats.clients > 0 {
98 + clients := float64(*stats.clients)
99 + if stats.signalAvg != nil {
100 + mx[px+"average_signal"] = int64(float64(*stats.signalAvg) / clients * precision)
101 + }
102 + if stats.rxBitrate != nil {
103 + mx[px+"bitrate_receive"] = int64(*stats.rxBitrate / clients * precision)
104 + }
105 + if stats.txBitrate != nil {
106 + mx[px+"bitrate_transmit"] = int64(*stats.txBitrate / clients * precision)
107 + }
108 }
109 }
110
@@ -146,7 +164,7 @@ func parseIwDevices(resp []byte) ([]*iwInterface, error) {
164 return apIfaces, nil
165 }
166
149 -func parseIwStationStatistics(resp []byte) (*stationStats, error) {
167 +func parseIwStationStatistics(resp []byte) *stationStats {
168 var stats stationStats
169
170 sc := bufio.NewScanner(bytes.NewReader(resp))
@@ -154,58 +172,49 @@ func parseIwStationStatistics(resp []byte) (*stationStats, error) {
172 for sc.Scan() {
173 line := strings.TrimSpace(sc.Text())
174
157 - var v float64
158 - var err error
159 -
175 switch {
176 case strings.HasPrefix(line, "Station"):
162 - stats.clients++
177 + stats.addInt64(&stats.clients, 1)
178 case strings.HasPrefix(line, "rx bytes:"):
164 - if v, err = get3rdValue(line); err == nil {
165 - stats.rxBytes += int64(v)
179 + if v, err := get3rdValue(line); err == nil {
180 + stats.addInt64(&stats.rxBytes, int64(v))
181 }
182 case strings.HasPrefix(line, "rx packets:"):
168 - if v, err = get3rdValue(line); err == nil {
169 - stats.rxPackets += int64(v)
183 + if v, err := get3rdValue(line); err == nil {
184 + stats.addInt64(&stats.rxPackets, int64(v))
185 }
186 case strings.HasPrefix(line, "tx bytes:"):
172 - if v, err = get3rdValue(line); err == nil {
173 - stats.txBytes += int64(v)
187 + if v, err := get3rdValue(line); err == nil {
188 + stats.addInt64(&stats.txBytes, int64(v))
189 }
190 case strings.HasPrefix(line, "tx packets:"):
176 - if v, err = get3rdValue(line); err == nil {
177 - stats.txPackets += int64(v)
191 + if v, err := get3rdValue(line); err == nil {
192 + stats.addInt64(&stats.txPackets, int64(v))
193 }
194 case strings.HasPrefix(line, "tx retries:"):
180 - if v, err = get3rdValue(line); err == nil {
181 - stats.txRetries += int64(v)
195 + if v, err := get3rdValue(line); err == nil {
196 + stats.addInt64(&stats.txRetries, int64(v))
197 }
198 case strings.HasPrefix(line, "tx failed:"):
184 - if v, err = get3rdValue(line); err == nil {
185 - stats.txFailed += int64(v)
199 + if v, err := get3rdValue(line); err == nil {
200 + stats.addInt64(&stats.txFailed, int64(v))
201 }
202 case strings.HasPrefix(line, "signal avg:"):
188 - if v, err = get3rdValue(line); err == nil {
189 - stats.signalAvg += int64(v)
203 + if v, err := get3rdValue(line); err == nil {
204 + stats.addInt64(&stats.signalAvg, int64(v))
205 }
206 case strings.HasPrefix(line, "tx bitrate:"):
192 - if v, err = get3rdValue(line); err == nil {
193 - stats.txBitrate += v
207 + if v, err := get3rdValue(line); err == nil {
208 + stats.addFloat64(&stats.txBitrate, v)
209 }
210 case strings.HasPrefix(line, "rx bitrate:"):
196 - if v, err = get3rdValue(line); err == nil {
197 - stats.rxBitrate += v
211 + if v, err := get3rdValue(line); err == nil {
212 + stats.addFloat64(&stats.rxBitrate, v)
213 }
199 - default:
200 - continue
201 - }
202 -
203 - if err != nil {
204 - return nil, fmt.Errorf("parsing line '%s': %v", line, err)
214 }
215 }
216
208 - return &stats, nil
217 + return &stats
218 }
219
220 func get3rdValue(line string) (float64, error) {
@@ -221,3 +230,17 @@ func get3rdValue(line string) (float64, error) {
230 }
231 return strconv.ParseFloat(v, 64)
232 }
233 +
234 +func (s *stationStats) addInt64(dst **int64, v int64) {
235 + if *dst == nil {
236 + *dst = new(int64)
237 + }
238 + **dst += v
239 +}
240 +
241 +func (s *stationStats) addFloat64(dst **float64, v float64) {
242 + if *dst == nil {
243 + *dst = new(float64)
244 + }
245 + **dst += v
246 +}
src/go/plugin/go.d/collector/ap/collector_test.go
+16 -16
@@ -166,26 +166,26 @@ func TestCollector_Collect(t *testing.T) {
166 prepareMock: prepareMockOk,
167 wantCharts: len(apChartsTmpl) * 2,
168 wantMetrics: map[string]int64{
169 - "ap_wlp1s0_testing_average_signal": -34000,
170 - "ap_wlp1s0_testing_bitrate_receive": 65500,
171 - "ap_wlp1s0_testing_bitrate_transmit": 65000,
172 - "ap_wlp1s0_testing_bw_received": 95117,
173 - "ap_wlp1s0_testing_bw_sent": 8270,
174 - "ap_wlp1s0_testing_clients": 2,
169 + "ap_wlp1s0_testing_average_signal": -50666,
170 + "ap_wlp1s0_testing_bitrate_receive": 49400,
171 + "ap_wlp1s0_testing_bitrate_transmit": 43333,
172 + "ap_wlp1s0_testing_bw_received": 101822,
173 + "ap_wlp1s0_testing_bw_sent": 9284,
174 + "ap_wlp1s0_testing_clients": 3,
175 "ap_wlp1s0_testing_issues_failures": 1,
176 "ap_wlp1s0_testing_issues_retries": 1,
177 - "ap_wlp1s0_testing_packets_received": 2531,
178 - "ap_wlp1s0_testing_packets_sent": 38,
179 - "ap_wlp1s1_testing_average_signal": -34000,
180 - "ap_wlp1s1_testing_bitrate_receive": 65500,
181 - "ap_wlp1s1_testing_bitrate_transmit": 65000,
182 - "ap_wlp1s1_testing_bw_received": 95117,
183 - "ap_wlp1s1_testing_bw_sent": 8270,
184 - "ap_wlp1s1_testing_clients": 2,
177 + "ap_wlp1s0_testing_packets_received": 2670,
178 + "ap_wlp1s0_testing_packets_sent": 51,
179 + "ap_wlp1s1_testing_average_signal": -50666,
180 + "ap_wlp1s1_testing_bitrate_receive": 49400,
181 + "ap_wlp1s1_testing_bitrate_transmit": 43333,
182 + "ap_wlp1s1_testing_bw_received": 101822,
183 + "ap_wlp1s1_testing_bw_sent": 9284,
184 + "ap_wlp1s1_testing_clients": 3,
185 "ap_wlp1s1_testing_issues_failures": 1,
186 "ap_wlp1s1_testing_issues_retries": 1,
187 - "ap_wlp1s1_testing_packets_received": 2531,
188 - "ap_wlp1s1_testing_packets_sent": 38,
187 + "ap_wlp1s1_testing_packets_received": 2670,
188 + "ap_wlp1s1_testing_packets_sent": 51,
189 },
190 },
191 "no ap devices": {
src/go/plugin/go.d/collector/ap/testdata/station_dump.txt
+29
@@ -56,3 +56,32 @@ Station fa:50:db:c1:1c:18 (on wlp1s0)
56 associated at [boottime]: 13440.167s
57 associated at: 1720706069520 ms
58 current time: 1720706075344 ms
59 +Station a7:2f:9e:d3:f0:34 (on wlp1s0)
60 + inactive time: 42324 ms
61 + rx bytes: 6705
62 + rx packets: 139
63 + tx bytes: 1014
64 + tx packets: 13
65 + tx retries: 0
66 + tx failed: 0
67 + rx drop misc: 0
68 + signal: -87 [-87] dBm
69 + signal avg: -84 [-84] dBm
70 + tx bitrate: (unknown)
71 + tx duration: 0 us
72 + rx bitrate: 17.2 MBit/s HE-MCS 1 HE-NSS 1 HE-GI 0 HE-DCM 0
73 + rx duration: 0 us
74 + authorized: yes
75 + authenticated: yes
76 + associated: yes
77 + preamble: long
78 + WMM/WME: yes
79 + MFP: no
80 + TDLS peer: no
81 + DTIM period: 2
82 + beacon interval:100
83 + short slot time:yes
84 + connected time: 2215 seconds
85 + associated at [boottime]: 3810.003s
86 + associated at: <timestamp> ms
87 + current time: <timestamp> ms