Files
ITWorx-Pulse-Public/internal/hostcollect/network.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

98 lines
2.7 KiB
Go

package hostcollect
import (
"sort"
"strings"
"github.com/itworx/pulse/internal/host"
)
// loopbackInterface is excluded: its counters describe the host talking to itself and
// only add noise to a network overview.
const loopbackInterface = "lo"
// parseNetDev reads /proc/net/dev.
//
// The format is two header lines followed by " name: rx... tx...". The name is
// separated by a colon that is NOT always followed by a space — a sufficiently large
// rx byte counter runs straight into it ("eth0:18446744073709551615 ...") — so the
// line is split on the first colon rather than on whitespace.
func parseNetDev(data []byte) []host.RawNetworkInterface {
all := lines(data)
interfaces := make([]host.RawNetworkInterface, 0, len(all))
for _, line := range all {
name, rest, found := strings.Cut(line, ":")
name = strings.TrimSpace(name)
if !found || name == "" || strings.Contains(name, " ") {
continue
}
fields := strings.Fields(rest)
if len(fields) < 16 {
continue
}
values := make([]uint64, 16)
malformed := false
for index := 0; index < 16; index++ {
value, ok := parseUint(fields[index])
if !ok {
malformed = true
break
}
values[index] = value
}
if malformed {
continue
}
interfaces = append(interfaces, host.RawNetworkInterface{
Name: truncate(name, 128),
RxBytes: values[0],
RxErrors: values[2],
RxDrops: values[3],
TxBytes: values[8],
TxErrors: values[10],
TxDrops: values[11],
})
}
return interfaces
}
func (c *Collector) network(warnings *warningSet) []host.RawNetworkInterface {
data, err := readLimited(c.procPath("net", "dev"), maxFileBytes)
if err != nil {
warnings.add(WarningNetworkUnavailable)
return nil
}
parsed := parseNetDev(data)
interfaces := make([]host.RawNetworkInterface, 0, len(parsed))
for _, item := range parsed {
if item.Name == loopbackInterface {
continue
}
item.State = c.interfaceState(item.Name)
interfaces = append(interfaces, item)
}
sort.Slice(interfaces, func(i, j int) bool { return interfaces[i].Name < interfaces[j].Name })
if len(interfaces) > c.options.HostLimits.MaxInterfaces {
interfaces = interfaces[:c.options.HostLimits.MaxInterfaces]
warnings.add(WarningNetworkTruncated)
}
if len(interfaces) == 0 {
return nil
}
return interfaces
}
// interfaceState reads the operational state sysfs publishes ("up", "down",
// "unknown"). A missing file is normal for virtual interfaces and simply yields no
// state rather than a warning.
func (c *Collector) interfaceState(name string) string {
if strings.ContainsAny(name, "/\\") {
return ""
}
state, err := readTrimmed(c.sysPath("class", "net", name, "operstate"), 64)
if err != nil {
return ""
}
return truncate(state, 32)
}