Public source validation / validate (push) Failing after 3m8s
98 lines
2.7 KiB
Go
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)
|
|
}
|