Files
govpp-snmp-agentx/src/vpp/vpp_stats.go

212 lines
5.5 KiB
Go

// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
package vpp
import (
"time"
"go.fd.io/govpp/api"
"govpp-snmp-agentx/logger"
)
// StatsCallback is called when interface stats are retrieved
type StatsCallback func(*api.InterfaceStats)
// StatsManager handles VPP statistics operations
type StatsManager struct {
client *VPPClient
interfaceManager *InterfaceManager
statsCallback StatsCallback
period time.Duration
running bool
}
// NewStatsManager creates a new stats manager
func NewStatsManager(client *VPPClient, interfaceManager *InterfaceManager) *StatsManager {
return &StatsManager{
client: client,
interfaceManager: interfaceManager,
period: time.Duration(*Period) * time.Second,
}
}
// SetStatsCallback sets the callback for stats updates
func (sm *StatsManager) SetStatsCallback(callback StatsCallback) {
sm.statsCallback = callback
}
// SetPeriod sets the polling period for stats
func (sm *StatsManager) SetPeriod(period time.Duration) {
sm.period = period
}
// StartStatsRoutine starts the stats polling routine
func (sm *StatsManager) StartStatsRoutine() {
if sm.running {
logger.Debugf("Stats routine already running")
return
}
sm.running = true
go sm.statsRoutine()
}
// StopStatsRoutine stops the stats polling routine
func (sm *StatsManager) StopStatsRoutine() {
sm.running = false
}
// GetInterfaceStats retrieves current interface statistics
func (sm *StatsManager) GetInterfaceStats() (*api.InterfaceStats, error) {
if !sm.client.IsConnected() {
return nil, &VPPError{Message: "VPP client not connected"}
}
statsConn := sm.client.GetStatsConnection()
if statsConn == nil {
return nil, &VPPError{Message: "Stats connection not available"}
}
stats := new(api.InterfaceStats)
if err := statsConn.GetInterfaceStats(stats); err != nil {
return nil, err
}
return stats, nil
}
// statsRoutine is the main stats polling loop
func (sm *StatsManager) statsRoutine() {
logger.Debugf("Starting VPP stats routine with period: %v", sm.period)
ticker := time.NewTicker(sm.period)
defer ticker.Stop()
var wasConnected = false
for {
if !sm.running {
logger.Debugf("Stats routine stopping")
break
}
// Check if we need to connect/reconnect
if !sm.client.IsConnected() {
if wasConnected {
logger.Printf("VPP connection lost, attempting reconnect...")
wasConnected = false
} else {
logger.Printf("VPP not connected, attempting connection...")
}
if err := sm.client.Connect(); err != nil {
logger.Printf("Failed to connect to VPP: %v", err)
time.Sleep(time.Second)
continue
}
logger.Printf("VPP connection established")
wasConnected = true
}
// Query stats if connected
if sm.client.IsConnected() {
if !sm.queryAndReportStats() {
logger.Printf("Stats query failed, marking connection as lost")
sm.client.Disconnect()
continue
}
}
// Wait for next tick
<-ticker.C
}
logger.Debugf("Stats routine ended")
}
// queryAndReportStats queries stats and calls the callback
func (sm *StatsManager) queryAndReportStats() bool {
// Check VPP liveness first
if !sm.client.CheckLiveness() {
logger.Debugf("VPP liveness check failed")
return false
}
// Get interface stats
stats, err := sm.GetInterfaceStats()
if err != nil {
logger.Printf("Failed to get interface stats: %v", err)
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
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 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,
iface.Tx.Packets, iface.Tx.Bytes)
}
// Call the callback to update the MIB
if sm.statsCallback != nil {
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
}