Filter interface data from stats segment by known vpp_iface; this avoids an issue in VPP where deleted interfaces remain in the stats segment

This commit is contained in:
Pim van Pelt
2025-11-23 11:05:05 +01:00
parent 5e36d5c926
commit b450e02b8d
3 changed files with 77 additions and 18 deletions

View File

@@ -48,7 +48,7 @@ func main() {
// Create VPP client and managers // Create VPP client and managers
vppClient := &vpp.VPPClient{} vppClient := &vpp.VPPClient{}
interfaceManager := vpp.NewInterfaceManager(vppClient) interfaceManager := vpp.NewInterfaceManager(vppClient)
statsManager := vpp.NewStatsManager(vppClient) statsManager := vpp.NewStatsManager(vppClient, interfaceManager)
// Set up interface event callback to update interface details // Set up interface event callback to update interface details
interfaceManager.SetEventCallback(interfaceMIB.UpdateInterfaceDetails) interfaceManager.SetEventCallback(interfaceMIB.UpdateInterfaceDetails)

View File

@@ -15,17 +15,19 @@ type StatsCallback func(*api.InterfaceStats)
// StatsManager handles VPP statistics operations // StatsManager handles VPP statistics operations
type StatsManager struct { type StatsManager struct {
client *VPPClient client *VPPClient
statsCallback StatsCallback interfaceManager *InterfaceManager
period time.Duration statsCallback StatsCallback
running bool period time.Duration
running bool
} }
// NewStatsManager creates a new stats manager // NewStatsManager creates a new stats manager
func NewStatsManager(client *VPPClient) *StatsManager { func NewStatsManager(client *VPPClient, interfaceManager *InterfaceManager) *StatsManager {
return &StatsManager{ return &StatsManager{
client: client, client: client,
period: time.Duration(*Period) * time.Second, interfaceManager: interfaceManager,
period: time.Duration(*Period) * time.Second,
} }
} }
@@ -139,11 +141,24 @@ func (sm *StatsManager) queryAndReportStats() bool {
return false 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 // 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 // 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", logger.Debugf("Interface %d (%s): RX %d pkts/%d bytes, TX %d pkts/%d bytes",
iface.InterfaceIndex, iface.InterfaceName, iface.InterfaceIndex, iface.InterfaceName,
iface.Rx.Packets, iface.Rx.Bytes, iface.Rx.Packets, iface.Rx.Bytes,
@@ -152,8 +167,45 @@ func (sm *StatsManager) queryAndReportStats() bool {
// Call the callback to update the MIB // Call the callback to update the MIB
if sm.statsCallback != nil { if sm.statsCallback != nil {
sm.statsCallback(stats) sm.statsCallback(filteredStats)
} }
return true 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
}

View File

@@ -11,7 +11,8 @@ import (
func TestNewStatsManager(t *testing.T) { func TestNewStatsManager(t *testing.T) {
client := &VPPClient{} client := &VPPClient{}
manager := NewStatsManager(client) interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
if manager == nil { if manager == nil {
t.Fatal("NewStatsManager() returned nil") t.Fatal("NewStatsManager() returned nil")
@@ -37,7 +38,8 @@ func TestNewStatsManager(t *testing.T) {
func TestStatsManagerSetStatsCallback(t *testing.T) { func TestStatsManagerSetStatsCallback(t *testing.T) {
client := &VPPClient{} client := &VPPClient{}
manager := NewStatsManager(client) interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
var callbackCalled bool var callbackCalled bool
var receivedStats *api.InterfaceStats var receivedStats *api.InterfaceStats
@@ -86,7 +88,8 @@ func TestStatsManagerSetStatsCallback(t *testing.T) {
func TestStatsManagerSetPeriod(t *testing.T) { func TestStatsManagerSetPeriod(t *testing.T) {
client := &VPPClient{} client := &VPPClient{}
manager := NewStatsManager(client) interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
newPeriod := 5 * time.Second newPeriod := 5 * time.Second
manager.SetPeriod(newPeriod) manager.SetPeriod(newPeriod)
@@ -98,7 +101,8 @@ func TestStatsManagerSetPeriod(t *testing.T) {
func TestStatsManagerStartStopStatsRoutine(t *testing.T) { func TestStatsManagerStartStopStatsRoutine(t *testing.T) {
client := &VPPClient{} client := &VPPClient{}
manager := NewStatsManager(client) interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
if manager.running { if manager.running {
t.Error("StatsManager should not be running initially") t.Error("StatsManager should not be running initially")
@@ -126,7 +130,8 @@ func TestStatsManagerStartStopStatsRoutine(t *testing.T) {
func TestStatsManagerGetInterfaceStatsWithoutConnection(t *testing.T) { func TestStatsManagerGetInterfaceStatsWithoutConnection(t *testing.T) {
client := &VPPClient{} client := &VPPClient{}
manager := NewStatsManager(client) interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
_, err := manager.GetInterfaceStats() _, err := manager.GetInterfaceStats()
if err == nil { if err == nil {
@@ -207,7 +212,8 @@ func TestStatsCallback(t *testing.T) {
func TestStatsManagerQueryAndReportStatsWithoutConnection(t *testing.T) { func TestStatsManagerQueryAndReportStatsWithoutConnection(t *testing.T) {
client := &VPPClient{} client := &VPPClient{}
manager := NewStatsManager(client) interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
// Should return false when not connected // Should return false when not connected
if manager.queryAndReportStats() { if manager.queryAndReportStats() {
@@ -217,7 +223,8 @@ func TestStatsManagerQueryAndReportStatsWithoutConnection(t *testing.T) {
func TestStatsManagerWithShortPeriod(t *testing.T) { func TestStatsManagerWithShortPeriod(t *testing.T) {
client := &VPPClient{} client := &VPPClient{}
manager := NewStatsManager(client) interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
// Set a very short period for testing // Set a very short period for testing
manager.SetPeriod(10 * time.Millisecond) manager.SetPeriod(10 * time.Millisecond)