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