mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
216 lines
6.2 KiB
Go
216 lines
6.2 KiB
Go
package monitoring
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"math"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
agentshost "github.com/rcourtman/pulse-go-rewrite/pkg/agents/host"
|
|
)
|
|
|
|
const (
|
|
maxAgentLibvirtDomains = 128
|
|
maxAgentLibvirtDomainNameBytes = 256
|
|
)
|
|
|
|
func (m *Monitor) normalizeAgentLibvirtInventory(
|
|
report *agentshost.LibvirtInventory,
|
|
hostID string,
|
|
previous *models.Host,
|
|
observedAt time.Time,
|
|
) *models.HostLibvirtInventory {
|
|
if report == nil {
|
|
if previous != nil && previous.Libvirt != nil {
|
|
preserved := *previous.Libvirt
|
|
preserved.Domains = append([]models.HostLibvirtDomain(nil), previous.Libvirt.Domains...)
|
|
return &preserved
|
|
}
|
|
return nil
|
|
}
|
|
|
|
previousByID := make(map[string]models.HostLibvirtDomain)
|
|
previousAt := time.Time{}
|
|
if previous != nil {
|
|
previousAt = previous.LastSeen
|
|
if previous.Libvirt != nil {
|
|
for _, domain := range previous.Libvirt.Domains {
|
|
previousByID[domain.ID] = domain
|
|
}
|
|
}
|
|
}
|
|
|
|
result := &models.HostLibvirtInventory{
|
|
Domains: make([]models.HostLibvirtDomain, 0, min(len(report.Domains), maxAgentLibvirtDomains)),
|
|
CollectedAt: observedAt.UTC(),
|
|
}
|
|
seen := make(map[string]struct{}, len(report.Domains))
|
|
for _, incoming := range report.Domains {
|
|
if len(result.Domains) == maxAgentLibvirtDomains {
|
|
break
|
|
}
|
|
name := strings.TrimSpace(incoming.Name)
|
|
if !validAgentLibvirtDomainName(name) {
|
|
continue
|
|
}
|
|
dedupeKey := strings.ToLower(name)
|
|
if _, exists := seen[dedupeKey]; exists {
|
|
continue
|
|
}
|
|
seen[dedupeKey] = struct{}{}
|
|
|
|
domain := models.HostLibvirtDomain{
|
|
ID: agentLibvirtDomainID(name),
|
|
Name: name,
|
|
State: normalizeAgentLibvirtState(incoming.State),
|
|
VCPUs: max(0, min(incoming.VCPUs, 4096)),
|
|
CPUTimeNanoseconds: incoming.CPUTimeNanoseconds,
|
|
MemoryCurrentBytes: max(int64(0), incoming.MemoryCurrentBytes),
|
|
MemoryMaximumBytes: max(int64(0), incoming.MemoryMaximumBytes),
|
|
NetworkRXBytes: incoming.NetworkRXBytes,
|
|
NetworkTXBytes: incoming.NetworkTXBytes,
|
|
DiskReadBytes: incoming.DiskReadBytes,
|
|
DiskWriteBytes: incoming.DiskWriteBytes,
|
|
}
|
|
if domain.MemoryMaximumBytes > 0 && domain.MemoryCurrentBytes > domain.MemoryMaximumBytes {
|
|
domain.MemoryCurrentBytes = domain.MemoryMaximumBytes
|
|
}
|
|
|
|
if prior, ok := previousByID[domain.ID]; ok {
|
|
domain.CPUUsagePercent, domain.CPUUsageValid = libvirtCPUUsagePercent(
|
|
prior,
|
|
domain,
|
|
observedAt.Sub(previousAt),
|
|
)
|
|
}
|
|
|
|
if m != nil && m.rateTracker != nil {
|
|
diskRead, diskWrite, netIn, netOut := m.rateTracker.CalculateRates(
|
|
"libvirt:"+strings.TrimSpace(hostID)+":"+domain.ID,
|
|
IOMetrics{
|
|
DiskRead: uint64ToInt64(domain.DiskReadBytes),
|
|
DiskWrite: uint64ToInt64(domain.DiskWriteBytes),
|
|
NetworkIn: uint64ToInt64(domain.NetworkRXBytes),
|
|
NetworkOut: uint64ToInt64(domain.NetworkTXBytes),
|
|
Timestamp: observedAt,
|
|
},
|
|
)
|
|
if diskRead >= 0 && diskWrite >= 0 && netIn >= 0 && netOut >= 0 {
|
|
domain.DiskReadRate = diskRead
|
|
domain.DiskWriteRate = diskWrite
|
|
domain.NetworkInRate = netIn
|
|
domain.NetworkOutRate = netOut
|
|
domain.IORatesValid = true
|
|
}
|
|
}
|
|
|
|
result.Domains = append(result.Domains, domain)
|
|
}
|
|
sort.Slice(result.Domains, func(i, j int) bool {
|
|
return strings.ToLower(result.Domains[i].Name) < strings.ToLower(result.Domains[j].Name)
|
|
})
|
|
return result
|
|
}
|
|
|
|
func validAgentLibvirtDomainName(name string) bool {
|
|
if name == "" || len(name) > maxAgentLibvirtDomainNameBytes || !utf8.ValidString(name) {
|
|
return false
|
|
}
|
|
for _, r := range name {
|
|
if r < 0x20 || r == 0x7f {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func normalizeAgentLibvirtState(value string) string {
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
case "running", "blocked", "paused", "shutting-down", "shutoff", "crashed", "suspended":
|
|
return strings.ToLower(strings.TrimSpace(value))
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func agentLibvirtDomainID(name string) string {
|
|
sum := sha256.Sum256([]byte(name))
|
|
return "domain-" + hex.EncodeToString(sum[:8])
|
|
}
|
|
|
|
func libvirtCPUUsagePercent(previous, current models.HostLibvirtDomain, elapsed time.Duration) (float64, bool) {
|
|
if elapsed <= 0 || current.CPUTimeNanoseconds < previous.CPUTimeNanoseconds {
|
|
return 0, false
|
|
}
|
|
vcpus := current.VCPUs
|
|
if vcpus <= 0 {
|
|
vcpus = 1
|
|
}
|
|
delta := current.CPUTimeNanoseconds - previous.CPUTimeNanoseconds
|
|
percent := (float64(delta) / float64(elapsed.Nanoseconds()) / float64(vcpus)) * 100
|
|
if math.IsNaN(percent) || math.IsInf(percent, 0) {
|
|
return 0, false
|
|
}
|
|
return math.Max(0, math.Min(100, percent)), true
|
|
}
|
|
|
|
func uint64ToInt64(value uint64) int64 {
|
|
if value > math.MaxInt64 {
|
|
return math.MaxInt64
|
|
}
|
|
return int64(value)
|
|
}
|
|
|
|
func libvirtDomainMetricID(hostID, domainID string) string {
|
|
return strings.TrimSpace(hostID) + ":libvirt:" + strings.TrimSpace(domainID)
|
|
}
|
|
|
|
func (m *Monitor) writeLibvirtDomainMetrics(host models.Host, observedAt time.Time) {
|
|
if m == nil || host.Libvirt == nil {
|
|
return
|
|
}
|
|
for _, domain := range host.Libvirt.Domains {
|
|
metricID := libvirtDomainMetricID(host.ID, domain.ID)
|
|
if domain.CPUUsageValid {
|
|
if m.metricsHistory != nil {
|
|
m.metricsHistory.AddGuestMetric(metricID, "cpu", domain.CPUUsagePercent, observedAt)
|
|
}
|
|
if m.metricsStore != nil {
|
|
m.metricsStore.Write("vm", metricID, "cpu", domain.CPUUsagePercent, observedAt)
|
|
}
|
|
}
|
|
if domain.MemoryMaximumBytes > 0 {
|
|
memoryPercent := math.Max(0, math.Min(
|
|
100,
|
|
(float64(domain.MemoryCurrentBytes)/float64(domain.MemoryMaximumBytes))*100,
|
|
))
|
|
if m.metricsHistory != nil {
|
|
m.metricsHistory.AddGuestMetric(metricID, "memory", memoryPercent, observedAt)
|
|
}
|
|
if m.metricsStore != nil {
|
|
m.metricsStore.Write("vm", metricID, "memory", memoryPercent, observedAt)
|
|
}
|
|
}
|
|
if !domain.IORatesValid {
|
|
continue
|
|
}
|
|
for metricType, value := range map[string]float64{
|
|
"netin": domain.NetworkInRate,
|
|
"netout": domain.NetworkOutRate,
|
|
"diskread": domain.DiskReadRate,
|
|
"diskwrite": domain.DiskWriteRate,
|
|
} {
|
|
if m.metricsHistory != nil {
|
|
m.metricsHistory.AddGuestMetric(metricID, metricType, value, observedAt)
|
|
}
|
|
if m.metricsStore != nil {
|
|
m.metricsStore.Write("vm", metricID, metricType, value, observedAt)
|
|
}
|
|
}
|
|
}
|
|
}
|