From b450e02b8d69cbc827dda49a4e45c81dd34c3a8d Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Sun, 23 Nov 2025 11:05:05 +0100 Subject: [PATCH] Filter interface data from stats segment by known vpp_iface; this avoids an issue in VPP where deleted interfaces remain in the stats segment --- src/main.go | 2 +- src/vpp/vpp_stats.go | 72 +++++++++++++++++++++++++++++++++------ src/vpp/vpp_stats_test.go | 21 ++++++++---- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/src/main.go b/src/main.go index 2ba7b7d..d1e64b4 100644 --- a/src/main.go +++ b/src/main.go @@ -48,7 +48,7 @@ func main() { // Create VPP client and managers vppClient := &vpp.VPPClient{} interfaceManager := vpp.NewInterfaceManager(vppClient) - statsManager := vpp.NewStatsManager(vppClient) + statsManager := vpp.NewStatsManager(vppClient, interfaceManager) // Set up interface event callback to update interface details interfaceManager.SetEventCallback(interfaceMIB.UpdateInterfaceDetails) diff --git a/src/vpp/vpp_stats.go b/src/vpp/vpp_stats.go index 2eb835c..83788bb 100644 --- a/src/vpp/vpp_stats.go +++ b/src/vpp/vpp_stats.go @@ -15,17 +15,19 @@ type StatsCallback func(*api.InterfaceStats) // StatsManager handles VPP statistics operations type StatsManager struct { - client *VPPClient - statsCallback StatsCallback - period time.Duration - running bool + client *VPPClient + interfaceManager *InterfaceManager + statsCallback StatsCallback + period time.Duration + running bool } // NewStatsManager creates a new stats manager -func NewStatsManager(client *VPPClient) *StatsManager { +func NewStatsManager(client *VPPClient, interfaceManager *InterfaceManager) *StatsManager { return &StatsManager{ - client: client, - period: time.Duration(*Period) * time.Second, + client: client, + interfaceManager: interfaceManager, + period: time.Duration(*Period) * time.Second, } } @@ -139,11 +141,24 @@ func (sm *StatsManager) queryAndReportStats() bool { return false } + // Filter out deleted interfaces by comparing with current interface list + filteredStats, err := sm.filterValidInterfaces(stats) + if err != nil { + logger.Printf("Failed to filter interface stats: %v", err) + return false + } + // Debug log basic info - logger.Debugf("Retrieved stats for %d interfaces", len(stats.Interfaces)) + originalCount := len(stats.Interfaces) + filteredCount := len(filteredStats.Interfaces) + logger.Debugf("Retrieved stats for %d interfaces, filtered to %d valid interfaces", originalCount, filteredCount) + + if originalCount > filteredCount { + logger.Debugf("Filtered out %d deleted interfaces", originalCount-filteredCount) + } // Debug logging for individual interfaces - for _, iface := range stats.Interfaces { + for _, iface := range filteredStats.Interfaces { logger.Debugf("Interface %d (%s): RX %d pkts/%d bytes, TX %d pkts/%d bytes", iface.InterfaceIndex, iface.InterfaceName, iface.Rx.Packets, iface.Rx.Bytes, @@ -152,8 +167,45 @@ func (sm *StatsManager) queryAndReportStats() bool { // Call the callback to update the MIB if sm.statsCallback != nil { - sm.statsCallback(stats) + sm.statsCallback(filteredStats) } return true } + +// filterValidInterfaces removes stats for deleted interfaces by comparing with current interface list +func (sm *StatsManager) filterValidInterfaces(stats *api.InterfaceStats) (*api.InterfaceStats, error) { + if sm.interfaceManager == nil { + logger.Debugf("No interface manager available, returning unfiltered stats") + return stats, nil + } + + // Get current interface details + currentInterfaces, err := sm.interfaceManager.GetAllInterfaceDetails() + if err != nil { + logger.Debugf("Failed to get current interface list: %v", err) + return stats, err + } + + // Create a map of valid sw_if_index values + validInterfaces := make(map[uint32]bool) + for _, iface := range currentInterfaces { + validInterfaces[uint32(iface.SwIfIndex)] = true + } + + // Filter the stats to only include valid interfaces + filteredStats := &api.InterfaceStats{ + Interfaces: make([]api.InterfaceCounters, 0, len(stats.Interfaces)), + } + + for _, ifaceStat := range stats.Interfaces { + if validInterfaces[ifaceStat.InterfaceIndex] { + filteredStats.Interfaces = append(filteredStats.Interfaces, ifaceStat) + } else { + logger.Debugf("Filtering out stats for deleted interface %d (%s)", + ifaceStat.InterfaceIndex, ifaceStat.InterfaceName) + } + } + + return filteredStats, nil +} diff --git a/src/vpp/vpp_stats_test.go b/src/vpp/vpp_stats_test.go index ea6e90a..f910089 100644 --- a/src/vpp/vpp_stats_test.go +++ b/src/vpp/vpp_stats_test.go @@ -11,7 +11,8 @@ import ( func TestNewStatsManager(t *testing.T) { client := &VPPClient{} - manager := NewStatsManager(client) + interfaceManager := NewInterfaceManager(client) + manager := NewStatsManager(client, interfaceManager) if manager == nil { t.Fatal("NewStatsManager() returned nil") @@ -37,7 +38,8 @@ func TestNewStatsManager(t *testing.T) { func TestStatsManagerSetStatsCallback(t *testing.T) { client := &VPPClient{} - manager := NewStatsManager(client) + interfaceManager := NewInterfaceManager(client) + manager := NewStatsManager(client, interfaceManager) var callbackCalled bool var receivedStats *api.InterfaceStats @@ -86,7 +88,8 @@ func TestStatsManagerSetStatsCallback(t *testing.T) { func TestStatsManagerSetPeriod(t *testing.T) { client := &VPPClient{} - manager := NewStatsManager(client) + interfaceManager := NewInterfaceManager(client) + manager := NewStatsManager(client, interfaceManager) newPeriod := 5 * time.Second manager.SetPeriod(newPeriod) @@ -98,7 +101,8 @@ func TestStatsManagerSetPeriod(t *testing.T) { func TestStatsManagerStartStopStatsRoutine(t *testing.T) { client := &VPPClient{} - manager := NewStatsManager(client) + interfaceManager := NewInterfaceManager(client) + manager := NewStatsManager(client, interfaceManager) if manager.running { t.Error("StatsManager should not be running initially") @@ -126,7 +130,8 @@ func TestStatsManagerStartStopStatsRoutine(t *testing.T) { func TestStatsManagerGetInterfaceStatsWithoutConnection(t *testing.T) { client := &VPPClient{} - manager := NewStatsManager(client) + interfaceManager := NewInterfaceManager(client) + manager := NewStatsManager(client, interfaceManager) _, err := manager.GetInterfaceStats() if err == nil { @@ -207,7 +212,8 @@ func TestStatsCallback(t *testing.T) { func TestStatsManagerQueryAndReportStatsWithoutConnection(t *testing.T) { client := &VPPClient{} - manager := NewStatsManager(client) + interfaceManager := NewInterfaceManager(client) + manager := NewStatsManager(client, interfaceManager) // Should return false when not connected if manager.queryAndReportStats() { @@ -217,7 +223,8 @@ func TestStatsManagerQueryAndReportStatsWithoutConnection(t *testing.T) { func TestStatsManagerWithShortPeriod(t *testing.T) { client := &VPPClient{} - manager := NewStatsManager(client) + interfaceManager := NewInterfaceManager(client) + manager := NewStatsManager(client, interfaceManager) // Set a very short period for testing manager.SetPeriod(10 * time.Millisecond)