From b1004d821d00538a13e316f54bc1fafb5e9f3943 Mon Sep 17 00:00:00 2001 From: Emin Umut Gercek Date: Sat, 23 Nov 2024 22:51:41 +0300 Subject: [PATCH 1/6] Add neighbor state Signed-off-by: Emin Umut Gercek --- collector/arp_linux.go | 48 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/collector/arp_linux.go b/collector/arp_linux.go index 7f417d824e..91d897882e 100644 --- a/collector/arp_linux.go +++ b/collector/arp_linux.go @@ -39,9 +39,25 @@ type arpCollector struct { fs procfs.FS deviceFilter deviceFilter entries *prometheus.Desc + states *prometheus.Desc logger *slog.Logger } +var neighborStatesMap = map[uint16]string{ + unix.NUD_INCOMPLETE: "incomplete", + unix.NUD_REACHABLE: "reachable", + unix.NUD_STALE: "stale", + unix.NUD_DELAY: "delay", + unix.NUD_PROBE: "probe", + unix.NUD_FAILED: "failed", + unix.NUD_PERMANENT: "permanent", +} + +type neighborState struct { + ip string + state string +} + func init() { registerCollector("arp", defaultEnabled, NewARPCollector) } @@ -61,6 +77,11 @@ func NewARPCollector(logger *slog.Logger) (Collector, error) { "ARP entries by device", []string{"device"}, nil, ), + states: prometheus.NewDesc( + prometheus.BuildFQName(namespace, "arp", "states"), + "ARP states by device", + []string{"device", "ip", "state"}, nil, + ), logger: logger, }, nil } @@ -75,19 +96,20 @@ func getTotalArpEntries(deviceEntries []procfs.ARPEntry) map[string]uint32 { return entries } -func getTotalArpEntriesRTNL() (map[string]uint32, error) { +func getArpEntriesNTRL() (map[string]uint32, map[string]neighborState, error) { conn, err := rtnetlink.Dial(nil) if err != nil { - return nil, err + return nil, nil, err } defer conn.Close() neighbors, err := conn.Neigh.List() if err != nil { - return nil, err + return nil, nil, err } ifIndexEntries := make(map[uint32]uint32) + ifIndexStates := make(map[uint32]neighborState) for _, n := range neighbors { // Neighbors will also contain IPv6 neighbors, but since this is purely an ARP collector, @@ -95,10 +117,12 @@ func getTotalArpEntriesRTNL() (map[string]uint32, error) { // of /proc/net/arp. if n.Family == unix.AF_INET && n.State&unix.NUD_NOARP == 0 { ifIndexEntries[n.Index]++ + ifIndexStates[n.Index] = neighborState{ip: n.Attributes.Address.String(), state: neighborStatesMap[n.State]} } } enumEntries := make(map[string]uint32) + enumStates := make(map[string]neighborState) // Convert interface indexes to names. for ifIndex, entryCount := range ifIndexEntries { @@ -107,22 +131,26 @@ func getTotalArpEntriesRTNL() (map[string]uint32, error) { if errors.Unwrap(err).Error() == "no such network interface" { continue } - return nil, err + return nil, nil, err } enumEntries[iface.Name] = entryCount + enumStates[iface.Name] = ifIndexStates[ifIndex] } - return enumEntries, nil + return enumEntries, enumStates, nil } func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { - var enumeratedEntry map[string]uint32 + var ( + enumeratedEntry map[string]uint32 + enumStates map[string]neighborState + ) if *arpNetlink { var err error - enumeratedEntry, err = getTotalArpEntriesRTNL() + enumeratedEntry, enumStates, err = getArpEntriesNTRL() if err != nil { return fmt.Errorf("could not get ARP entries: %w", err) } @@ -141,6 +169,12 @@ func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { } ch <- prometheus.MustNewConstMetric( c.entries, prometheus.GaugeValue, float64(entryCount), device) + + if *arpNetlink { + s := enumStates[device] + ch <- prometheus.MustNewConstMetric( + c.states, prometheus.GaugeValue, 1, device, s.ip, s.state) + } } return nil From 2246604659855065903c50066b67c5409a461b8d Mon Sep 17 00:00:00 2001 From: Emin Umut Gercek Date: Fri, 6 Dec 2024 21:40:13 +0300 Subject: [PATCH 2/6] Move to count of states Signed-off-by: Emin Umut Gercek --- collector/arp_linux.go | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/collector/arp_linux.go b/collector/arp_linux.go index 91d897882e..29fa22bafd 100644 --- a/collector/arp_linux.go +++ b/collector/arp_linux.go @@ -80,7 +80,7 @@ func NewARPCollector(logger *slog.Logger) (Collector, error) { states: prometheus.NewDesc( prometheus.BuildFQName(namespace, "arp", "states"), "ARP states by device", - []string{"device", "ip", "state"}, nil, + []string{"device", "state"}, nil, ), logger: logger, }, nil @@ -96,7 +96,7 @@ func getTotalArpEntries(deviceEntries []procfs.ARPEntry) map[string]uint32 { return entries } -func getArpEntriesNTRL() (map[string]uint32, map[string]neighborState, error) { +func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]int, error) { conn, err := rtnetlink.Dial(nil) if err != nil { return nil, nil, err @@ -109,7 +109,7 @@ func getArpEntriesNTRL() (map[string]uint32, map[string]neighborState, error) { } ifIndexEntries := make(map[uint32]uint32) - ifIndexStates := make(map[uint32]neighborState) + ifIndexStates := make(map[uint32]map[string]int) for _, n := range neighbors { // Neighbors will also contain IPv6 neighbors, but since this is purely an ARP collector, @@ -117,12 +117,17 @@ func getArpEntriesNTRL() (map[string]uint32, map[string]neighborState, error) { // of /proc/net/arp. if n.Family == unix.AF_INET && n.State&unix.NUD_NOARP == 0 { ifIndexEntries[n.Index]++ - ifIndexStates[n.Index] = neighborState{ip: n.Attributes.Address.String(), state: neighborStatesMap[n.State]} + + _, ok := ifIndexStates[n.Index] + if !ok { + ifIndexStates[n.Index] = make(map[string]int) + } + ifIndexStates[n.Index][neighborStatesMap[n.State]]++ } } enumEntries := make(map[string]uint32) - enumStates := make(map[string]neighborState) + enumStates := make(map[string]map[string]int) // Convert interface indexes to names. for ifIndex, entryCount := range ifIndexEntries { @@ -144,13 +149,13 @@ func getArpEntriesNTRL() (map[string]uint32, map[string]neighborState, error) { func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { var ( enumeratedEntry map[string]uint32 - enumStates map[string]neighborState + enumStates map[string]map[string]int ) if *arpNetlink { var err error - enumeratedEntry, enumStates, err = getArpEntriesNTRL() + enumeratedEntry, enumStates, err = getArpEntriesRTNL() if err != nil { return fmt.Errorf("could not get ARP entries: %w", err) } @@ -171,9 +176,11 @@ func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { c.entries, prometheus.GaugeValue, float64(entryCount), device) if *arpNetlink { - s := enumStates[device] - ch <- prometheus.MustNewConstMetric( - c.states, prometheus.GaugeValue, 1, device, s.ip, s.state) + states := enumStates[device] + for state, count := range states { + ch <- prometheus.MustNewConstMetric( + c.states, prometheus.GaugeValue, float64(count), device, state) + } } } From 6b0be659b97d452b6465dc31167803e763c6ef2d Mon Sep 17 00:00:00 2001 From: Emin Umut Gercek Date: Sun, 2 Feb 2025 14:04:07 +0300 Subject: [PATCH 3/6] Fix linter Signed-off-by: Emin Umut Gercek --- collector/arp_linux.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/collector/arp_linux.go b/collector/arp_linux.go index dfa3b4314e..98c83c9903 100644 --- a/collector/arp_linux.go +++ b/collector/arp_linux.go @@ -51,11 +51,6 @@ var neighborStatesMap = map[uint16]string{ unix.NUD_PERMANENT: "permanent", } -type neighborState struct { - ip string - state string -} - func init() { registerCollector("arp", defaultEnabled, NewARPCollector) } From ed49ea92d8790e853414de7e0b6d4c4e687ba61f Mon Sep 17 00:00:00 2001 From: Emin Umut Gercek Date: Sun, 2 Feb 2025 16:27:44 +0300 Subject: [PATCH 4/6] Fix `NUD_NOARP` skip Signed-off-by: Emin Umut Gercek --- collector/arp_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collector/arp_linux.go b/collector/arp_linux.go index 98c83c9903..d0be924d63 100644 --- a/collector/arp_linux.go +++ b/collector/arp_linux.go @@ -110,7 +110,7 @@ func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]int, error) { for _, n := range neighbors { // Skip entries which have state NUD_NOARP to conform to output of /proc/net/arp. - if n.State&unix.NUD_NOARP != unix.NUD_NOARP { + if n.State&unix.NUD_NOARP == unix.NUD_NOARP { continue } From 81e132146db80d9ba09d9dc4ba5cb1e6152da865 Mon Sep 17 00:00:00 2001 From: Emin Umut Gercek Date: Sun, 2 Feb 2025 17:39:50 +0300 Subject: [PATCH 5/6] Implement reviews Signed-off-by: Emin Umut Gercek --- collector/arp_linux.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/collector/arp_linux.go b/collector/arp_linux.go index d0be924d63..6348f0a369 100644 --- a/collector/arp_linux.go +++ b/collector/arp_linux.go @@ -89,7 +89,7 @@ func getTotalArpEntries(deviceEntries []procfs.ARPEntry) map[string]uint32 { return entries } -func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]int, error) { +func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]uint32, error) { conn, err := rtnl.Dial(nil) if err != nil { return nil, nil, err @@ -106,7 +106,7 @@ func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]int, error) { // Map of interface name to ARP neighbor count. entries := make(map[string]uint32) // Map of map[InterfaceName]map[StateName]int - states := make(map[string]map[string]int) + states := make(map[string]map[string]uint32) for _, n := range neighbors { // Skip entries which have state NUD_NOARP to conform to output of /proc/net/arp. @@ -116,9 +116,8 @@ func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]int, error) { entries[n.Interface.Name]++ - _, ok := states[n.Interface.Name] - if !ok { - states[n.Interface.Name] = make(map[string]int) + if _, ok := states[n.Interface.Name]; !ok { + states[n.Interface.Name] = make(map[string]uint32) } states[n.Interface.Name][neighborStatesMap[n.State]]++ @@ -130,7 +129,7 @@ func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]int, error) { func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { var ( enumeratedEntry map[string]uint32 - enumStates map[string]map[string]int + enumStates map[string]map[string]uint32 ) if *arpNetlink { @@ -157,8 +156,7 @@ func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { c.entries, prometheus.GaugeValue, float64(entryCount), device) if *arpNetlink { - states := enumStates[device] - for state, count := range states { + for state, count := range enumStates[device] { ch <- prometheus.MustNewConstMetric( c.states, prometheus.GaugeValue, float64(count), device, state) } From 3ca9f7d4825fea6d98653c11d1d41c58ee16b717 Mon Sep 17 00:00:00 2001 From: Emin Umut Gercek Date: Sun, 2 Feb 2025 18:30:43 +0300 Subject: [PATCH 6/6] review: Optimize `getArpEntriesRTNL` via deferring string lookup Signed-off-by: Emin Umut Gercek --- collector/arp_linux.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/collector/arp_linux.go b/collector/arp_linux.go index 6348f0a369..e5a94e9f34 100644 --- a/collector/arp_linux.go +++ b/collector/arp_linux.go @@ -89,7 +89,7 @@ func getTotalArpEntries(deviceEntries []procfs.ARPEntry) map[string]uint32 { return entries } -func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]uint32, error) { +func getArpEntriesRTNL() (map[string]uint32, map[string]map[uint16]uint32, error) { conn, err := rtnl.Dial(nil) if err != nil { return nil, nil, err @@ -105,8 +105,8 @@ func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]uint32, error // Map of interface name to ARP neighbor count. entries := make(map[string]uint32) - // Map of map[InterfaceName]map[StateName]int - states := make(map[string]map[string]uint32) + // Map of map[InterfaceName]map[StateCode]CountOfTheState + states := make(map[string]map[uint16]uint32) for _, n := range neighbors { // Skip entries which have state NUD_NOARP to conform to output of /proc/net/arp. @@ -117,10 +117,10 @@ func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]uint32, error entries[n.Interface.Name]++ if _, ok := states[n.Interface.Name]; !ok { - states[n.Interface.Name] = make(map[string]uint32) + states[n.Interface.Name] = make(map[uint16]uint32) } - states[n.Interface.Name][neighborStatesMap[n.State]]++ + states[n.Interface.Name][n.State]++ } return entries, states, nil @@ -129,7 +129,7 @@ func getArpEntriesRTNL() (map[string]uint32, map[string]map[string]uint32, error func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { var ( enumeratedEntry map[string]uint32 - enumStates map[string]map[string]uint32 + enumStates map[string]map[uint16]uint32 ) if *arpNetlink { @@ -158,7 +158,7 @@ func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { if *arpNetlink { for state, count := range enumStates[device] { ch <- prometheus.MustNewConstMetric( - c.states, prometheus.GaugeValue, float64(count), device, state) + c.states, prometheus.GaugeValue, float64(count), device, neighborStatesMap[state]) } } }