diff options
Diffstat (limited to 'src/go/plugin/go.d/modules/ap/collect.go')
-rw-r--r-- | src/go/plugin/go.d/modules/ap/collect.go | 221 |
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) +} |