fix(ap): handle SSIDs containing whitespace (#22472)
* Initial plan * fix(ap): handle SSIDs with whitespace in iw dev output parsing Agent-Logs-Url: https://github.com/netdata/netdata/sessions/dda66a5c-6ba0-4c7c-87fd-f7d4fab3a147 Co-authored-by: ilyam8 <22274335+ilyam8@users.noreply.github.com> * style(ap): gofmt collector_test.go --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ilyam8 <22274335+ilyam8@users.noreply.github.com> Co-authored-by: ilyam8 <ilya@netdata.cloud>
Copilot committed
May 12, 2026 at 20:17 UTC
43f27e1bbad4944e48b0ebfc6eea1e630e3bbdaa
4 files changed
+53
-12
src/go/plugin/go.d/collector/ap/charts.go
+1
-1
@@ -123,7 +123,7 @@ func (c *Collector) addInterfaceCharts(dev *iwInterface) {
123
{Key: "ssid", Value: dev.ssid},
124
}
125
for _, dim := range chart.Dims {
126
- dim.ID = fmt.Sprintf(dim.ID, dev.name, dev.ssid)
126
+ dim.ID = fmt.Sprintf(dim.ID, dev.name, cleanSSID(dev.ssid))
127
}
128
}
129
src/go/plugin/go.d/collector/ap/collect.go
+4
-4
@@ -70,7 +70,7 @@ func (c *Collector) collect() (map[string]int64, error) {
70
c.addInterfaceCharts(iface)
71
}
72
73
- px := fmt.Sprintf("ap_%s_%s_", iface.name, iface.ssid)
73
+ px := fmt.Sprintf("ap_%s_%s_", iface.name, cleanSSID(iface.ssid))
74
75
if stats.clients != nil {
76
mx[px+"clients"] = *stats.clients
@@ -139,11 +139,11 @@ func parseIwDevices(resp []byte) ([]*iwInterface, error) {
139
ifaces[name] = iface
140
}
141
case strings.HasPrefix(line, "ssid") && iface != nil:
142
- parts := strings.Fields(line)
143
- if len(parts) != 2 {
142
+ ssid := strings.TrimSpace(strings.TrimPrefix(line, "ssid"))
143
+ if ssid == "" {
144
return nil, fmt.Errorf("invalid ssid line: '%s'", line)
145
}
146
- iface.ssid = parts[1]
146
+ iface.ssid = ssid
147
case strings.HasPrefix(line, "type") && iface != nil:
148
parts := strings.Fields(line)
149
if len(parts) != 2 {
src/go/plugin/go.d/collector/ap/collector_test.go
+36
-7
@@ -22,17 +22,19 @@ var (
22
23
dataIwDevManaged, _ = os.ReadFile("testdata/iw_dev_managed.txt")
24
25
- dataIwDevAP, _ = os.ReadFile("testdata/iw_dev_ap.txt")
26
- dataIwStationDump, _ = os.ReadFile("testdata/station_dump.txt")
25
+ dataIwDevAP, _ = os.ReadFile("testdata/iw_dev_ap.txt")
26
+ dataIwDevAPSsidWithSpaces, _ = os.ReadFile("testdata/iw_dev_ap_ssid_with_spaces.txt")
27
+ dataIwStationDump, _ = os.ReadFile("testdata/station_dump.txt")
28
)
29
30
func Test_testDataIsValid(t *testing.T) {
31
for name, data := range map[string][]byte{
31
- "dataConfigJSON": dataConfigJSON,
32
- "dataConfigYAML": dataConfigYAML,
33
- "dataIwDevManaged": dataIwDevManaged,
34
- "dataIwDevAP": dataIwDevAP,
35
- "dataIwStationDump": dataIwStationDump,
32
+ "dataConfigJSON": dataConfigJSON,
33
+ "dataConfigYAML": dataConfigYAML,
34
+ "dataIwDevManaged": dataIwDevManaged,
35
+ "dataIwDevAP": dataIwDevAP,
36
+ "dataIwDevAPSsidWithSpaces": dataIwDevAPSsidWithSpaces,
37
+ "dataIwStationDump": dataIwStationDump,
38
} {
39
require.NotNil(t, data, name)
40
}
@@ -124,6 +126,10 @@ func TestCollector_Check(t *testing.T) {
126
wantFail: false,
127
prepareMock: prepareMockOk,
128
},
129
+ "success case ssid with spaces": {
130
+ wantFail: false,
131
+ prepareMock: prepareMockOkSsidWithSpaces,
132
+ },
133
"no ap devices": {
134
wantFail: true,
135
prepareMock: prepareMockNoAPDevices,
@@ -188,6 +194,22 @@ func TestCollector_Collect(t *testing.T) {
194
"ap_wlp1s1_testing_packets_sent": 51,
195
},
196
},
197
+ "success case ssid with spaces": {
198
+ prepareMock: prepareMockOkSsidWithSpaces,
199
+ wantCharts: len(apChartsTmpl) * 1,
200
+ wantMetrics: map[string]int64{
201
+ "ap_wlp1s0_My_AP_1_average_signal": -50666,
202
+ "ap_wlp1s0_My_AP_1_bitrate_receive": 49400,
203
+ "ap_wlp1s0_My_AP_1_bitrate_transmit": 43333,
204
+ "ap_wlp1s0_My_AP_1_bw_received": 101822,
205
+ "ap_wlp1s0_My_AP_1_bw_sent": 9284,
206
+ "ap_wlp1s0_My_AP_1_clients": 3,
207
+ "ap_wlp1s0_My_AP_1_issues_failures": 1,
208
+ "ap_wlp1s0_My_AP_1_issues_retries": 1,
209
+ "ap_wlp1s0_My_AP_1_packets_received": 2670,
210
+ "ap_wlp1s0_My_AP_1_packets_sent": 51,
211
+ },
212
+ },
213
"no ap devices": {
214
prepareMock: prepareMockNoAPDevices,
215
wantMetrics: nil,
@@ -229,6 +251,13 @@ func prepareMockOk() *mockIwExec {
251
}
252
}
253
254
+func prepareMockOkSsidWithSpaces() *mockIwExec {
255
+ return &mockIwExec{
256
+ devicesData: dataIwDevAPSsidWithSpaces,
257
+ stationStatsData: dataIwStationDump,
258
+ }
259
+}
260
+
261
func prepareMockNoAPDevices() *mockIwExec {
262
return &mockIwExec{
263
devicesData: dataIwDevManaged,
src/go/plugin/go.d/collector/ap/testdata/iw_dev_ap_ssid_with_spaces.txt
new
+12
@@ -0,0 +1,12 @@
1
+phy#0
2
+ Interface wlp1s0
3
+ ifindex 2
4
+ wdev 0x1
5
+ addr 28:cd:c4:b8:63:cb
6
+ ssid My AP 1
7
+ type AP
8
+ channel 1 (2412 MHz), width: 20 MHz, center1: 2412 MHz
9
+ txpower 20.00 dBm
10
+ multicast TXQ:
11
+ qsz-byt qsz-pkt flows drops marks overlmt hashcol tx-bytes tx-packets
12
+ 0 0 2 0 0 0 0 16447 226