mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-25 04:33:03 +00:00
bd6f77e093
Tighten v5-to-v6 upgrade safety, release installability, provider MSP mode handling, AI cost accounting, metrics flushing, and frontend guardrails for the v6.0.0 GA candidate.
104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
package monitoring
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
vmAgentMemCacheTTL = 60 * time.Second // Cache guest-agent /proc/meminfo reads.
|
|
vmAgentMemRequestTTL = 3 * time.Second // Bound guest-agent file-read latency.
|
|
vmAgentMemNegativeTTL = 5 * time.Minute // Back off on unsupported or failing guests.
|
|
vmAgentMemNegativeKnownGuestTTL = 30 * time.Second // Retry sooner for guests we know are non-Windows.
|
|
vmAgentMemCleanupMaxAge = 2 * vmAgentMemNegativeTTL
|
|
)
|
|
|
|
type agentMemCacheEntry struct {
|
|
available uint64
|
|
negative bool
|
|
fetchedAt time.Time
|
|
}
|
|
|
|
type guestAgentMemAvailableClient interface {
|
|
GetVMMemAvailableFromAgent(ctx context.Context, node string, vmid int) (uint64, error)
|
|
}
|
|
|
|
func guestMemoryCacheKey(instanceName, node string, vmid int) string {
|
|
return fmt.Sprintf("%s/%s/%d", instanceName, node, vmid)
|
|
}
|
|
|
|
func (m *Monitor) getVMAgentMemAvailable(ctx context.Context, client PVEClientInterface, instanceName, node string, vmid int) (uint64, error) {
|
|
memClient, ok := client.(guestAgentMemAvailableClient)
|
|
if !ok {
|
|
return 0, fmt.Errorf("guest agent meminfo fallback unsupported")
|
|
}
|
|
if node == "" || vmid <= 0 {
|
|
return 0, fmt.Errorf("invalid arguments for guest agent meminfo lookup")
|
|
}
|
|
|
|
cacheKey := guestMemoryCacheKey(instanceName, node, vmid)
|
|
now := time.Now()
|
|
|
|
m.rrdCacheMu.RLock()
|
|
if entry, ok := m.vmAgentMemCache[cacheKey]; ok {
|
|
ttl := vmAgentMemCacheTTL
|
|
if entry.negative {
|
|
ttl = m.vmAgentMemNegativeCacheTTL(instanceName, node, vmid)
|
|
}
|
|
if now.Sub(entry.fetchedAt) < ttl {
|
|
m.rrdCacheMu.RUnlock()
|
|
if entry.negative {
|
|
return 0, fmt.Errorf("guest agent meminfo fallback unavailable")
|
|
}
|
|
return entry.available, nil
|
|
}
|
|
}
|
|
m.rrdCacheMu.RUnlock()
|
|
|
|
requestCtx, cancel := context.WithTimeout(ctx, vmAgentMemRequestTTL)
|
|
defer cancel()
|
|
|
|
available, err := memClient.GetVMMemAvailableFromAgent(requestCtx, node, vmid)
|
|
m.rrdCacheMu.Lock()
|
|
defer m.rrdCacheMu.Unlock()
|
|
if m.vmAgentMemCache == nil {
|
|
m.vmAgentMemCache = make(map[string]agentMemCacheEntry)
|
|
}
|
|
if err != nil || available == 0 {
|
|
m.vmAgentMemCache[cacheKey] = agentMemCacheEntry{negative: true, fetchedAt: now}
|
|
if err == nil {
|
|
err = fmt.Errorf("guest agent meminfo fallback unavailable")
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
m.vmAgentMemCache[cacheKey] = agentMemCacheEntry{available: available, fetchedAt: now}
|
|
return available, nil
|
|
}
|
|
|
|
func (m *Monitor) vmAgentMemNegativeCacheTTL(instanceName, node string, vmid int) time.Duration {
|
|
if m == nil {
|
|
return vmAgentMemNegativeTTL
|
|
}
|
|
|
|
key := guestMetadataCacheKey(instanceName, node, vmid)
|
|
m.guestMetadataMu.RLock()
|
|
entry, ok := m.guestMetadataCache[key]
|
|
m.guestMetadataMu.RUnlock()
|
|
if !ok {
|
|
return vmAgentMemNegativeTTL
|
|
}
|
|
|
|
osName := strings.ToLower(strings.TrimSpace(entry.osName))
|
|
osVersion := strings.ToLower(strings.TrimSpace(entry.osVersion))
|
|
if osName == "" && osVersion == "" {
|
|
return vmAgentMemNegativeTTL
|
|
}
|
|
if strings.Contains(osName, "windows") || strings.Contains(osVersion, "windows") {
|
|
return vmAgentMemNegativeTTL
|
|
}
|
|
return vmAgentMemNegativeKnownGuestTTL
|
|
}
|