This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user