summaryrefslogtreecommitdiffstats
path: root/src/go/plugin/go.d/modules/ap/collect.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/go/plugin/go.d/modules/ap/collect.go')
-rw-r--r--src/go/plugin/go.d/modules/ap/collect.go221
1 files changed, 221 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/ap/collect.go b/src/go/plugin/go.d/modules/ap/collect.go
new file mode 100644
index 000000000..ba32f3ef7
--- /dev/null
+++ b/src/go/plugin/go.d/modules/ap/collect.go
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package ap
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+const precision = 1000
+
+type iwInterface struct {
+ name string
+ ssid string
+ typ string
+}
+
+type stationStats struct {
+ clients int64
+ rxBytes int64
+ rxPackets int64
+ txBytes int64
+ txPackets int64
+ txRetries int64
+ txFailed int64
+ signalAvg int64
+ txBitrate float64
+ rxBitrate float64
+}
+
+func (a *AP) collect() (map[string]int64, error) {
+ bs, err := a.exec.devices()
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO: call this periodically, not on every data collection
+ apInterfaces, err := parseIwDevices(bs)
+ if err != nil {
+ return nil, fmt.Errorf("parsing AP interfaces: %v", err)
+ }
+
+ if len(apInterfaces) == 0 {
+ return nil, errors.New("no type AP interfaces found")
+ }
+
+ mx := make(map[string]int64)
+ seen := make(map[string]bool)
+
+ for _, iface := range apInterfaces {
+ bs, err = a.exec.stationStatistics(iface.name)
+ if err != nil {
+ return nil, fmt.Errorf("getting station statistics for %s: %v", iface, err)
+ }
+
+ stats, err := parseIwStationStatistics(bs)
+ if err != nil {
+ return nil, fmt.Errorf("parsing station statistics for %s: %v", iface, err)
+ }
+
+ key := fmt.Sprintf("%s-%s", iface.name, iface.ssid)
+
+ seen[key] = true
+
+ if _, ok := a.seenIfaces[key]; !ok {
+ a.seenIfaces[key] = iface
+ a.addInterfaceCharts(iface)
+ }
+
+ px := fmt.Sprintf("ap_%s_%s_", iface.name, iface.ssid)
+
+ mx[px+"clients"] = stats.clients
+ mx[px+"bw_received"] = stats.rxBytes
+ mx[px+"bw_sent"] = stats.txBytes
+ mx[px+"packets_received"] = stats.rxPackets
+ mx[px+"packets_sent"] = stats.txPackets
+ mx[px+"issues_retries"] = stats.txRetries
+ mx[px+"issues_failures"] = stats.txFailed
+ mx[px+"average_signal"], mx[px+"bitrate_receive"], mx[px+"bitrate_transmit"] = 0, 0, 0
+ if clients := float64(stats.clients); clients > 0 {
+ mx[px+"average_signal"] = int64(float64(stats.signalAvg) / clients * precision)
+ mx[px+"bitrate_receive"] = int64(stats.rxBitrate / clients * precision)
+ mx[px+"bitrate_transmit"] = int64(stats.txBitrate / clients * precision)
+ }
+ }
+
+ for key, iface := range a.seenIfaces {
+ if !seen[key] {
+ delete(a.seenIfaces, key)
+ a.removeInterfaceCharts(iface)
+ }
+ }
+
+ return mx, nil
+}
+
+func parseIwDevices(resp []byte) ([]*iwInterface, error) {
+ ifaces := make(map[string]*iwInterface)
+ var iface *iwInterface
+
+ sc := bufio.NewScanner(bytes.NewReader(resp))
+
+ for sc.Scan() {
+ line := strings.TrimSpace(sc.Text())
+
+ switch {
+ case strings.HasPrefix(line, "Interface"):
+ parts := strings.Fields(line)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("invalid interface line: '%s'", line)
+ }
+ name := parts[1]
+ if _, ok := ifaces[name]; !ok {
+ iface = &iwInterface{name: name}
+ ifaces[name] = iface
+ }
+ case strings.HasPrefix(line, "ssid") && iface != nil:
+ parts := strings.Fields(line)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("invalid ssid line: '%s'", line)
+ }
+ iface.ssid = parts[1]
+ case strings.HasPrefix(line, "type") && iface != nil:
+ parts := strings.Fields(line)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("invalid type line: '%s'", line)
+ }
+ iface.typ = parts[1]
+ }
+ }
+
+ var apIfaces []*iwInterface
+
+ for _, iface := range ifaces {
+ if strings.ToLower(iface.typ) == "ap" {
+ apIfaces = append(apIfaces, iface)
+ }
+ }
+
+ return apIfaces, nil
+}
+
+func parseIwStationStatistics(resp []byte) (*stationStats, error) {
+ var stats stationStats
+
+ sc := bufio.NewScanner(bytes.NewReader(resp))
+
+ for sc.Scan() {
+ line := strings.TrimSpace(sc.Text())
+
+ var v float64
+ var err error
+
+ switch {
+ case strings.HasPrefix(line, "Station"):
+ stats.clients++
+ case strings.HasPrefix(line, "rx bytes:"):
+ if v, err = get3rdValue(line); err == nil {
+ stats.rxBytes += int64(v)
+ }
+ case strings.HasPrefix(line, "rx packets:"):
+ if v, err = get3rdValue(line); err == nil {
+ stats.rxPackets += int64(v)
+ }
+ case strings.HasPrefix(line, "tx bytes:"):
+ if v, err = get3rdValue(line); err == nil {
+ stats.txBytes += int64(v)
+ }
+ case strings.HasPrefix(line, "tx packets:"):
+ if v, err = get3rdValue(line); err == nil {
+ stats.txPackets += int64(v)
+ }
+ case strings.HasPrefix(line, "tx retries:"):
+ if v, err = get3rdValue(line); err == nil {
+ stats.txRetries += int64(v)
+ }
+ case strings.HasPrefix(line, "tx failed:"):
+ if v, err = get3rdValue(line); err == nil {
+ stats.txFailed += int64(v)
+ }
+ case strings.HasPrefix(line, "signal avg:"):
+ if v, err = get3rdValue(line); err == nil {
+ stats.signalAvg += int64(v)
+ }
+ case strings.HasPrefix(line, "tx bitrate:"):
+ if v, err = get3rdValue(line); err == nil {
+ stats.txBitrate += v
+ }
+ case strings.HasPrefix(line, "rx bitrate:"):
+ if v, err = get3rdValue(line); err == nil {
+ stats.rxBitrate += v
+ }
+ default:
+ continue
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("parsing line '%s': %v", line, err)
+ }
+ }
+
+ return &stats, nil
+}
+
+func get3rdValue(line string) (float64, error) {
+ parts := strings.Fields(line)
+ if len(parts) < 3 {
+ return 0.0, errors.New("invalid format")
+ }
+
+ v := parts[2]
+
+ if v == "-" {
+ return 0.0, nil
+ }
+ return strconv.ParseFloat(v, 64)
+}