master
go 246 lines 5.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 //go:build linux
4
5 package ap
6
7 import (
8 "bufio"
9 "bytes"
10 "errors"
11 "fmt"
12 "strconv"
13 "strings"
14 )
15
16 const precision = 1000
17
18 type iwInterface struct {
19 name string
20 ssid string
21 typ string
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
35 }
36
37 func (c *Collector) collect() (map[string]int64, error) {
38 bs, err := c.exec.devices()
39 if err != nil {
40 return nil, err
41 }
42
43 // TODO: call this periodically, not on every data collection
44 apInterfaces, err := parseIwDevices(bs)
45 if err != nil {
46 return nil, fmt.Errorf("parsing AP interfaces: %v", err)
47 }
48
49 if len(apInterfaces) == 0 {
50 return nil, errors.New("no type AP interfaces found")
51 }
52
53 mx := make(map[string]int64)
54 seen := make(map[string]bool)
55
56 for _, iface := range apInterfaces {
57 bs, err = c.exec.stationStatistics(iface.name)
58 if err != nil {
59 return nil, fmt.Errorf("getting station statistics for %s: %v", iface, err)
60 }
61
62 stats := parseIwStationStatistics(bs)
63
64 key := fmt.Sprintf("%s-%s", iface.name, iface.ssid)
65
66 seen[key] = true
67
68 if _, ok := c.seenIfaces[key]; !ok {
69 c.seenIfaces[key] = iface
70 c.addInterfaceCharts(iface)
71 }
72
73 px := fmt.Sprintf("ap_%s_%s_", iface.name, cleanSSID(iface.ssid))
74
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
111 for key, iface := range c.seenIfaces {
112 if !seen[key] {
113 delete(c.seenIfaces, key)
114 c.removeInterfaceCharts(iface)
115 }
116 }
117
118 return mx, nil
119 }
120
121 func parseIwDevices(resp []byte) ([]*iwInterface, error) {
122 ifaces := make(map[string]*iwInterface)
123 var iface *iwInterface
124
125 sc := bufio.NewScanner(bytes.NewReader(resp))
126
127 for sc.Scan() {
128 line := strings.TrimSpace(sc.Text())
129
130 switch {
131 case strings.HasPrefix(line, "Interface"):
132 parts := strings.Fields(line)
133 if len(parts) != 2 {
134 return nil, fmt.Errorf("invalid interface line: '%s'", line)
135 }
136 name := parts[1]
137 if _, ok := ifaces[name]; !ok {
138 iface = &iwInterface{name: name}
139 ifaces[name] = iface
140 }
141 case strings.HasPrefix(line, "ssid") && iface != nil:
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 = ssid
147 case strings.HasPrefix(line, "type") && iface != nil:
148 parts := strings.Fields(line)
149 if len(parts) != 2 {
150 return nil, fmt.Errorf("invalid type line: '%s'", line)
151 }
152 iface.typ = parts[1]
153 }
154 }
155
156 var apIfaces []*iwInterface
157
158 for _, iface := range ifaces {
159 if strings.ToLower(iface.typ) == "ap" {
160 apIfaces = append(apIfaces, iface)
161 }
162 }
163
164 return apIfaces, nil
165 }
166
167 func parseIwStationStatistics(resp []byte) *stationStats {
168 var stats stationStats
169
170 sc := bufio.NewScanner(bytes.NewReader(resp))
171
172 for sc.Scan() {
173 line := strings.TrimSpace(sc.Text())
174
175 switch {
176 case strings.HasPrefix(line, "Station"):
177 stats.addInt64(&stats.clients, 1)
178 case strings.HasPrefix(line, "rx bytes:"):
179 if v, err := get3rdValue(line); err == nil {
180 stats.addInt64(&stats.rxBytes, int64(v))
181 }
182 case strings.HasPrefix(line, "rx packets:"):
183 if v, err := get3rdValue(line); err == nil {
184 stats.addInt64(&stats.rxPackets, int64(v))
185 }
186 case strings.HasPrefix(line, "tx bytes:"):
187 if v, err := get3rdValue(line); err == nil {
188 stats.addInt64(&stats.txBytes, int64(v))
189 }
190 case strings.HasPrefix(line, "tx packets:"):
191 if v, err := get3rdValue(line); err == nil {
192 stats.addInt64(&stats.txPackets, int64(v))
193 }
194 case strings.HasPrefix(line, "tx retries:"):
195 if v, err := get3rdValue(line); err == nil {
196 stats.addInt64(&stats.txRetries, int64(v))
197 }
198 case strings.HasPrefix(line, "tx failed:"):
199 if v, err := get3rdValue(line); err == nil {
200 stats.addInt64(&stats.txFailed, int64(v))
201 }
202 case strings.HasPrefix(line, "signal avg:"):
203 if v, err := get3rdValue(line); err == nil {
204 stats.addInt64(&stats.signalAvg, int64(v))
205 }
206 case strings.HasPrefix(line, "tx bitrate:"):
207 if v, err := get3rdValue(line); err == nil {
208 stats.addFloat64(&stats.txBitrate, v)
209 }
210 case strings.HasPrefix(line, "rx bitrate:"):
211 if v, err := get3rdValue(line); err == nil {
212 stats.addFloat64(&stats.rxBitrate, v)
213 }
214 }
215 }
216
217 return &stats
218 }
219
220 func get3rdValue(line string) (float64, error) {
221 parts := strings.Fields(line)
222 if len(parts) < 3 {
223 return 0.0, errors.New("invalid format")
224 }
225
226 v := parts[2]
227
228 if v == "-" {
229 return 0.0, nil
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 }