212 lines
5.5 KiB
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
|
|
}
|