mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-23 14:06:18 +01:00
updates
This commit is contained in:
@@ -101,7 +101,7 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) {
|
|||||||
agent.dockerManager = newDockerManager()
|
agent.dockerManager = newDockerManager()
|
||||||
|
|
||||||
// initialize pve manager
|
// initialize pve manager
|
||||||
agent.pveManager = newPVEManager(agent)
|
agent.pveManager = newPVEManager()
|
||||||
|
|
||||||
// initialize system info
|
// initialize system info
|
||||||
agent.refreshSystemDetails()
|
agent.refreshSystemDetails()
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func createTestCacheData() *system.CombinedData {
|
func createTestCacheData() *system.CombinedData {
|
||||||
|
var stats = container.Stats{}
|
||||||
|
stats.Name = "test-container"
|
||||||
|
stats.Cpu = 10.5
|
||||||
|
stats.Mem = 1073741824 // 1GB
|
||||||
return &system.CombinedData{
|
return &system.CombinedData{
|
||||||
Stats: system.Stats{
|
Stats: system.Stats{
|
||||||
Cpu: 50.5,
|
Cpu: 50.5,
|
||||||
@@ -24,10 +28,7 @@ func createTestCacheData() *system.CombinedData {
|
|||||||
AgentVersion: "0.12.0",
|
AgentVersion: "0.12.0",
|
||||||
},
|
},
|
||||||
Containers: []*container.Stats{
|
Containers: []*container.Stats{
|
||||||
{
|
&stats,
|
||||||
Name: "test-container",
|
|
||||||
Cpu: 25.0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,11 +397,12 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo, cacheTimeM
|
|||||||
// add empty values if they doesn't exist in map
|
// add empty values if they doesn't exist in map
|
||||||
stats, initialized := dm.containerStatsMap[ctr.IdShort]
|
stats, initialized := dm.containerStatsMap[ctr.IdShort]
|
||||||
if !initialized {
|
if !initialized {
|
||||||
stats = &container.Stats{Name: name, Id: ctr.IdShort, Image: ctr.Image}
|
stats = &container.Stats{Image: ctr.Image}
|
||||||
dm.containerStatsMap[ctr.IdShort] = stats
|
dm.containerStatsMap[ctr.IdShort] = stats
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.Id = ctr.IdShort
|
stats.Id = ctr.IdShort
|
||||||
|
stats.Name = name
|
||||||
|
|
||||||
statusText, health := parseDockerStatus(ctr.Status)
|
statusText, health := parseDockerStatus(ctr.Status)
|
||||||
stats.Status = statusText
|
stats.Status = statusText
|
||||||
|
|||||||
@@ -269,17 +269,16 @@ func TestValidateCpuPercentage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateContainerStatsValues(t *testing.T) {
|
func TestUpdateContainerStatsValues(t *testing.T) {
|
||||||
stats := &container.Stats{
|
var stats = container.Stats{}
|
||||||
Name: "test-container",
|
stats.Name = "test-container"
|
||||||
Cpu: 0.0,
|
stats.Cpu = 0.0
|
||||||
Mem: 0.0,
|
stats.Mem = 0.0
|
||||||
NetworkSent: 0.0,
|
stats.NetworkSent = 0.0
|
||||||
NetworkRecv: 0.0,
|
stats.NetworkRecv = 0.0
|
||||||
PrevReadTime: time.Time{},
|
stats.PrevReadTime = time.Time{}
|
||||||
}
|
|
||||||
|
|
||||||
testTime := time.Now()
|
testTime := time.Now()
|
||||||
updateContainerStatsValues(stats, 75.5, 1048576, 524288, 262144, testTime)
|
updateContainerStatsValues(&stats, 75.5, 1048576, 524288, 262144, testTime)
|
||||||
|
|
||||||
// Check CPU percentage (should be rounded to 2 decimals)
|
// Check CPU percentage (should be rounded to 2 decimals)
|
||||||
assert.Equal(t, 75.5, stats.Cpu)
|
assert.Equal(t, 75.5, stats.Cpu)
|
||||||
@@ -446,12 +445,11 @@ func TestCalculateNetworkStats(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := &container.Stats{
|
var stats = container.Stats{}
|
||||||
PrevReadTime: time.Now().Add(-time.Second), // 1 second ago
|
stats.PrevReadTime = time.Now().Add(-time.Second) // 1 second ago
|
||||||
}
|
|
||||||
|
|
||||||
// Test with initialized container
|
// Test with initialized container
|
||||||
sent, recv := dm.calculateNetworkStats(ctr, apiStats, stats, true, "test-container", cacheTimeMs)
|
sent, recv := dm.calculateNetworkStats(ctr, apiStats, &stats, true, "test-container", cacheTimeMs)
|
||||||
|
|
||||||
// Should return calculated byte rates per second
|
// Should return calculated byte rates per second
|
||||||
assert.GreaterOrEqual(t, sent, uint64(0))
|
assert.GreaterOrEqual(t, sent, uint64(0))
|
||||||
@@ -460,7 +458,7 @@ func TestCalculateNetworkStats(t *testing.T) {
|
|||||||
// Cycle and test one-direction change (Tx only) is reflected independently
|
// Cycle and test one-direction change (Tx only) is reflected independently
|
||||||
dm.cycleNetworkDeltasForCacheTime(cacheTimeMs)
|
dm.cycleNetworkDeltasForCacheTime(cacheTimeMs)
|
||||||
apiStats.Networks["eth0"] = container.NetworkStats{TxBytes: 2500, RxBytes: 1800} // +500 Tx only
|
apiStats.Networks["eth0"] = container.NetworkStats{TxBytes: 2500, RxBytes: 1800} // +500 Tx only
|
||||||
sent, recv = dm.calculateNetworkStats(ctr, apiStats, stats, true, "test-container", cacheTimeMs)
|
sent, recv = dm.calculateNetworkStats(ctr, apiStats, &stats, true, "test-container", cacheTimeMs)
|
||||||
assert.Greater(t, sent, uint64(0))
|
assert.Greater(t, sent, uint64(0))
|
||||||
assert.Equal(t, uint64(0), recv)
|
assert.Equal(t, uint64(0), recv)
|
||||||
}
|
}
|
||||||
@@ -726,7 +724,8 @@ func TestMemoryStatsEdgeCases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerStatsInitialization(t *testing.T) {
|
func TestContainerStatsInitialization(t *testing.T) {
|
||||||
stats := &container.Stats{Name: "test-container"}
|
var stats = container.Stats{}
|
||||||
|
stats.Name = "test-container"
|
||||||
|
|
||||||
// Verify initial values
|
// Verify initial values
|
||||||
assert.Equal(t, "test-container", stats.Name)
|
assert.Equal(t, "test-container", stats.Name)
|
||||||
@@ -738,7 +737,7 @@ func TestContainerStatsInitialization(t *testing.T) {
|
|||||||
|
|
||||||
// Test updating values
|
// Test updating values
|
||||||
testTime := time.Now()
|
testTime := time.Now()
|
||||||
updateContainerStatsValues(stats, 45.67, 2097152, 1048576, 524288, testTime)
|
updateContainerStatsValues(&stats, 45.67, 2097152, 1048576, 524288, testTime)
|
||||||
|
|
||||||
assert.Equal(t, 45.67, stats.Cpu)
|
assert.Equal(t, 45.67, stats.Cpu)
|
||||||
assert.Equal(t, 2.0, stats.Mem)
|
assert.Equal(t, 2.0, stats.Mem)
|
||||||
@@ -816,12 +815,11 @@ func TestNetworkStatsCalculationWithRealData(t *testing.T) {
|
|||||||
|
|
||||||
// Use exact timing for deterministic results
|
// Use exact timing for deterministic results
|
||||||
exactly1000msAgo := time.Now().Add(-1000 * time.Millisecond)
|
exactly1000msAgo := time.Now().Add(-1000 * time.Millisecond)
|
||||||
stats := &container.Stats{
|
var stats = container.Stats{}
|
||||||
PrevReadTime: exactly1000msAgo,
|
stats.PrevReadTime = exactly1000msAgo
|
||||||
}
|
|
||||||
|
|
||||||
// First call sets baseline
|
// First call sets baseline
|
||||||
sent1, recv1 := dm.calculateNetworkStats(ctr, apiStats1, stats, true, "test", cacheTimeMs)
|
sent1, recv1 := dm.calculateNetworkStats(ctr, apiStats1, &stats, true, "test", cacheTimeMs)
|
||||||
assert.Equal(t, uint64(0), sent1)
|
assert.Equal(t, uint64(0), sent1)
|
||||||
assert.Equal(t, uint64(0), recv1)
|
assert.Equal(t, uint64(0), recv1)
|
||||||
|
|
||||||
@@ -836,7 +834,7 @@ func TestNetworkStatsCalculationWithRealData(t *testing.T) {
|
|||||||
expectedRecvRate := deltaRecv * 1000 / expectedElapsedMs // Should be exactly 1000000
|
expectedRecvRate := deltaRecv * 1000 / expectedElapsedMs // Should be exactly 1000000
|
||||||
|
|
||||||
// Second call with changed data
|
// Second call with changed data
|
||||||
sent2, recv2 := dm.calculateNetworkStats(ctr, apiStats2, stats, true, "test", cacheTimeMs)
|
sent2, recv2 := dm.calculateNetworkStats(ctr, apiStats2, &stats, true, "test", cacheTimeMs)
|
||||||
|
|
||||||
// Should be exactly the expected rates (no tolerance needed)
|
// Should be exactly the expected rates (no tolerance needed)
|
||||||
assert.Equal(t, expectedSentRate, sent2)
|
assert.Equal(t, expectedSentRate, sent2)
|
||||||
@@ -847,9 +845,9 @@ func TestNetworkStatsCalculationWithRealData(t *testing.T) {
|
|||||||
stats.PrevReadTime = time.Now().Add(-1 * time.Millisecond)
|
stats.PrevReadTime = time.Now().Add(-1 * time.Millisecond)
|
||||||
apiStats1.Networks["eth0"] = container.NetworkStats{TxBytes: 0, RxBytes: 0}
|
apiStats1.Networks["eth0"] = container.NetworkStats{TxBytes: 0, RxBytes: 0}
|
||||||
apiStats2.Networks["eth0"] = container.NetworkStats{TxBytes: 10 * 1024 * 1024 * 1024, RxBytes: 0} // 10GB delta
|
apiStats2.Networks["eth0"] = container.NetworkStats{TxBytes: 10 * 1024 * 1024 * 1024, RxBytes: 0} // 10GB delta
|
||||||
_, _ = dm.calculateNetworkStats(ctr, apiStats1, stats, true, "test", cacheTimeMs) // baseline
|
_, _ = dm.calculateNetworkStats(ctr, apiStats1, &stats, true, "test", cacheTimeMs) // baseline
|
||||||
dm.cycleNetworkDeltasForCacheTime(cacheTimeMs)
|
dm.cycleNetworkDeltasForCacheTime(cacheTimeMs)
|
||||||
sent3, recv3 := dm.calculateNetworkStats(ctr, apiStats2, stats, true, "test", cacheTimeMs)
|
sent3, recv3 := dm.calculateNetworkStats(ctr, apiStats2, &stats, true, "test", cacheTimeMs)
|
||||||
assert.Equal(t, uint64(0), sent3)
|
assert.Equal(t, uint64(0), sent3)
|
||||||
assert.Equal(t, uint64(0), recv3)
|
assert.Equal(t, uint64(0), recv3)
|
||||||
}
|
}
|
||||||
@@ -883,8 +881,9 @@ func TestContainerStatsEndToEndWithRealData(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize container stats
|
// Initialize container stats
|
||||||
stats := &container.Stats{Name: "jellyfin"}
|
var stats = container.Stats{}
|
||||||
dm.containerStatsMap[ctr.IdShort] = stats
|
stats.Name = "jellyfin"
|
||||||
|
dm.containerStatsMap[ctr.IdShort] = &stats
|
||||||
|
|
||||||
// Test individual components that we can verify
|
// Test individual components that we can verify
|
||||||
usedMemory, memErr := calculateMemoryUsage(&apiStats, false)
|
usedMemory, memErr := calculateMemoryUsage(&apiStats, false)
|
||||||
|
|||||||
110
agent/pve.go
110
agent/pve.go
@@ -14,16 +14,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type pveManager struct {
|
type pveManager struct {
|
||||||
client *proxmox.Client // Client to query PVE API
|
client *proxmox.Client // Client to query PVE API
|
||||||
nodeName string // Cluster node name
|
nodeName string // Cluster node name
|
||||||
cpuCount int // CPU count on node
|
cpuCount int // CPU count on node
|
||||||
containerStatsMap map[string]*container.Stats // Keeps track of container stats
|
nodeStatsMap map[string]*container.PveNodeStats // Keeps track of pve node stats
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns stats for all running VMs/LXCs
|
// Returns stats for all running VMs/LXCs
|
||||||
func (pm *pveManager) getPVEStats() ([]*container.Stats, error) {
|
func (pm *pveManager) getPVEStats() ([]*container.PveNodeStats, error) {
|
||||||
if pm.client == nil {
|
if pm.client == nil {
|
||||||
slog.Info("PVE client not configured")
|
|
||||||
return nil, errors.New("PVE client not configured")
|
return nil, errors.New("PVE client not configured")
|
||||||
}
|
}
|
||||||
cluster, err := pm.client.Cluster(context.Background())
|
cluster, err := pm.client.Cluster(context.Background())
|
||||||
@@ -31,42 +30,37 @@ func (pm *pveManager) getPVEStats() ([]*container.Stats, error) {
|
|||||||
slog.Error("Error getting cluster", "err", err)
|
slog.Error("Error getting cluster", "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
slog.Info("PVE cluster", "cluster", cluster)
|
|
||||||
resources, err := cluster.Resources(context.Background(), "vm")
|
resources, err := cluster.Resources(context.Background(), "vm")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error getting resources", "err", err, "resources", resources)
|
slog.Error("Error getting resources", "err", err, "resources", resources)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
slog.Info("PVE resources", "resources", resources)
|
|
||||||
containersLength := len(resources)
|
containersLength := len(resources)
|
||||||
slog.Info("PVE containers length", "containersLength", containersLength)
|
|
||||||
containerIds := make(map[string]struct{}, containersLength)
|
containerIds := make(map[string]struct{}, containersLength)
|
||||||
|
|
||||||
// only include running vms and lxcs on selected node
|
// only include running vms and lxcs on selected node
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
if resource.Node == pm.nodeName && resource.Status == "running" {
|
if resource.Node == pm.nodeName && resource.Status == "running" {
|
||||||
slog.Info("PVE resource", "resource", resource)
|
|
||||||
containerIds[resource.ID] = struct{}{}
|
containerIds[resource.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remove invalid container stats
|
// remove invalid container stats
|
||||||
for id := range pm.containerStatsMap {
|
for id := range pm.nodeStatsMap {
|
||||||
if _, exists := containerIds[id]; !exists {
|
if _, exists := containerIds[id]; !exists {
|
||||||
delete(pm.containerStatsMap, id)
|
delete(pm.nodeStatsMap, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate stats
|
// populate stats
|
||||||
stats := make([]*container.Stats, 0, len(containerIds))
|
stats := make([]*container.PveNodeStats, 0, len(containerIds))
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
// slog.Info("PVE resource", "resource", resource)
|
|
||||||
if _, exists := containerIds[resource.ID]; !exists {
|
if _, exists := containerIds[resource.ID]; !exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resourceStats, initialized := pm.containerStatsMap[resource.ID]
|
resourceStats, initialized := pm.nodeStatsMap[resource.ID]
|
||||||
if !initialized {
|
if !initialized {
|
||||||
resourceStats = &container.Stats{}
|
resourceStats = &container.PveNodeStats{}
|
||||||
pm.containerStatsMap[resource.ID] = resourceStats
|
pm.nodeStatsMap[resource.ID] = resourceStats
|
||||||
}
|
}
|
||||||
// reset current stats
|
// reset current stats
|
||||||
resourceStats.Cpu = 0
|
resourceStats.Cpu = 0
|
||||||
@@ -77,7 +71,7 @@ func (pm *pveManager) getPVEStats() ([]*container.Stats, error) {
|
|||||||
// Store resource ID (e.g. "qemu/100") in .Id (cbor key 7, json:"-")
|
// Store resource ID (e.g. "qemu/100") in .Id (cbor key 7, json:"-")
|
||||||
resourceStats.Id = resource.ID
|
resourceStats.Id = resource.ID
|
||||||
// Store type (e.g. "qemu" or "lxc") in .Image (cbor key 8, json:"-")
|
// Store type (e.g. "qemu" or "lxc") in .Image (cbor key 8, json:"-")
|
||||||
resourceStats.Image = resource.Type
|
resourceStats.Type = resource.Type
|
||||||
// PVE limits (cbor-only, for pve_vms table)
|
// PVE limits (cbor-only, for pve_vms table)
|
||||||
resourceStats.MaxCPU = resource.MaxCPU
|
resourceStats.MaxCPU = resource.MaxCPU
|
||||||
resourceStats.MaxMem = resource.MaxMem
|
resourceStats.MaxMem = resource.MaxMem
|
||||||
@@ -98,30 +92,30 @@ func (pm *pveManager) getPVEStats() ([]*container.Stats, error) {
|
|||||||
resourceStats.Mem = float64(resource.Mem)
|
resourceStats.Mem = float64(resource.Mem)
|
||||||
resourceStats.Bandwidth = [2]uint64{uint64(sent_delta), uint64(recv_delta)}
|
resourceStats.Bandwidth = [2]uint64{uint64(sent_delta), uint64(recv_delta)}
|
||||||
|
|
||||||
slog.Info("PVE resource stats", "resourceStats", resourceStats)
|
|
||||||
|
|
||||||
stats = append(stats, resourceStats)
|
stats = append(stats, resourceStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("PVE stats", "stats", stats)
|
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new PVE manager
|
// Creates a new PVE manager - may return nil if required environment variables are not set or if there is an error connecting to the API
|
||||||
func newPVEManager(_ *Agent) *pveManager {
|
func newPVEManager() *pveManager {
|
||||||
slog.Info("Creating new PVE manager")
|
|
||||||
url, exists := GetEnv("PROXMOX_URL")
|
url, exists := GetEnv("PROXMOX_URL")
|
||||||
if !exists {
|
if !exists {
|
||||||
url = "https://localhost:8006/api2/json"
|
url = "https://localhost:8006/api2/json"
|
||||||
}
|
}
|
||||||
nodeName, nodeNameExists := GetEnv("PROXMOX_NODE")
|
const nodeEnvVar = "PROXMOX_NODE"
|
||||||
tokenID, tokenIDExists := GetEnv("PROXMOX_TOKENID")
|
const tokenIDEnvVar = "PROXMOX_TOKENID"
|
||||||
secret, secretExists := GetEnv("PROXMOX_SECRET")
|
const secretEnvVar = "PROXMOX_SECRET"
|
||||||
|
|
||||||
slog.Info("PROXMOX_URL", "url", url)
|
nodeName, nodeNameExists := GetEnv(nodeEnvVar)
|
||||||
slog.Info("PROXMOX_NODE", "nodeName", nodeName)
|
tokenID, tokenIDExists := GetEnv(tokenIDEnvVar)
|
||||||
slog.Info("PROXMOX_TOKENID", "tokenID", tokenID)
|
secret, secretExists := GetEnv(secretEnvVar)
|
||||||
slog.Info("PROXMOX_SECRET", "secret", secret)
|
|
||||||
|
if !nodeNameExists || !tokenIDExists || !secretExists {
|
||||||
|
slog.Debug("Proxmox env vars unset", nodeEnvVar, nodeNameExists, tokenIDEnvVar, tokenIDExists, secretEnvVar, secretExists)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// PROXMOX_INSECURE_TLS defaults to true; set to "false" to enable TLS verification
|
// PROXMOX_INSECURE_TLS defaults to true; set to "false" to enable TLS verification
|
||||||
insecureTLS := true
|
insecureTLS := true
|
||||||
@@ -129,40 +123,32 @@ func newPVEManager(_ *Agent) *pveManager {
|
|||||||
insecureTLS = val != "false"
|
insecureTLS = val != "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
var client *proxmox.Client
|
httpClient := http.Client{
|
||||||
if nodeNameExists && tokenIDExists && secretExists {
|
Transport: &http.Transport{
|
||||||
httpClient := http.Client{
|
TLSClientConfig: &tls.Config{
|
||||||
Transport: &http.Transport{
|
InsecureSkipVerify: insecureTLS,
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: insecureTLS,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
client = proxmox.NewClient(url,
|
}
|
||||||
proxmox.WithHTTPClient(&httpClient),
|
client := proxmox.NewClient(url,
|
||||||
proxmox.WithAPIToken(tokenID, secret),
|
proxmox.WithHTTPClient(&httpClient),
|
||||||
)
|
proxmox.WithAPIToken(tokenID, secret),
|
||||||
} else {
|
)
|
||||||
slog.Error("Env variables not set")
|
|
||||||
client = nil
|
pveManager := pveManager{
|
||||||
|
client: client,
|
||||||
|
nodeName: nodeName,
|
||||||
|
nodeStatsMap: make(map[string]*container.PveNodeStats),
|
||||||
}
|
}
|
||||||
|
|
||||||
pveManager := &pveManager{
|
|
||||||
client: client,
|
|
||||||
nodeName: nodeName,
|
|
||||||
containerStatsMap: make(map[string]*container.Stats),
|
|
||||||
}
|
|
||||||
// Retrieve node cpu count
|
// Retrieve node cpu count
|
||||||
if client != nil {
|
node, err := client.Node(context.Background(), nodeName)
|
||||||
slog.Info("Getting node CPU count", "nodeName", nodeName)
|
if err != nil {
|
||||||
node, err := client.Node(context.Background(), nodeName)
|
slog.Error("Error connecting to Proxmox", "err", err)
|
||||||
if err != nil {
|
return nil
|
||||||
pveManager.client = nil
|
} else {
|
||||||
slog.Error("Error getting node", "err", err)
|
pveManager.cpuCount = node.CPUInfo.CPUs
|
||||||
} else {
|
|
||||||
pveManager.cpuCount = node.CPUInfo.CPUs
|
|
||||||
slog.Info("Node CPU count", "cpuCount", pveManager.cpuCount)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return pveManager
|
|
||||||
|
return &pveManager
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -559,6 +559,10 @@ func TestWriteToSessionEncoding(t *testing.T) {
|
|||||||
|
|
||||||
// Helper function to create test data for encoding tests
|
// Helper function to create test data for encoding tests
|
||||||
func createTestCombinedData() *system.CombinedData {
|
func createTestCombinedData() *system.CombinedData {
|
||||||
|
var stats = container.Stats{}
|
||||||
|
stats.Name = "test-container"
|
||||||
|
stats.Cpu = 10.5
|
||||||
|
stats.Mem = 1073741824 // 1GB
|
||||||
return &system.CombinedData{
|
return &system.CombinedData{
|
||||||
Stats: system.Stats{
|
Stats: system.Stats{
|
||||||
Cpu: 25.5,
|
Cpu: 25.5,
|
||||||
@@ -577,11 +581,7 @@ func createTestCombinedData() *system.CombinedData {
|
|||||||
AgentVersion: "0.12.0",
|
AgentVersion: "0.12.0",
|
||||||
},
|
},
|
||||||
Containers: []*container.Stats{
|
Containers: []*container.Stats{
|
||||||
{
|
&stats,
|
||||||
Name: "test-container",
|
|
||||||
Cpu: 10.5,
|
|
||||||
Mem: 1073741824, // 1GB
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,25 +127,38 @@ var DockerHealthStrings = map[string]DockerHealth{
|
|||||||
"unhealthy": DockerHealthUnhealthy,
|
"unhealthy": DockerHealthUnhealthy,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Docker container stats
|
// SharedCoreMetrics contains fields that are common to both container Stats and PveNodeStats
|
||||||
type Stats struct {
|
type SharedCoreMetrics struct {
|
||||||
Name string `json:"n" cbor:"0,keyasint"`
|
Name string `json:"n" cbor:"0,keyasint"`
|
||||||
Cpu float64 `json:"c" cbor:"1,keyasint"`
|
Cpu float64 `json:"c" cbor:"1,keyasint"`
|
||||||
Mem float64 `json:"m" cbor:"2,keyasint"`
|
Mem float64 `json:"m" cbor:"2,keyasint"`
|
||||||
NetworkSent float64 `json:"ns,omitzero" cbor:"3,keyasint,omitzero"` // deprecated 0.18.3 (MB) - keep field for old agents/records
|
NetworkSent float64 `json:"ns,omitzero" cbor:"3,keyasint,omitzero"` // deprecated 0.18.3 (MB) - keep field for old agents/records
|
||||||
NetworkRecv float64 `json:"nr,omitzero" cbor:"4,keyasint,omitzero"` // deprecated 0.18.3 (MB) - keep field for old agents/records
|
NetworkRecv float64 `json:"nr,omitzero" cbor:"4,keyasint,omitzero"` // deprecated 0.18.3 (MB) - keep field for old agents/records
|
||||||
Bandwidth [2]uint64 `json:"b,omitzero" cbor:"9,keyasint,omitzero"` // [sent bytes, recv bytes]
|
Id string `json:"-" cbor:"7,keyasint"`
|
||||||
|
Bandwidth [2]uint64 `json:"b,omitzero" cbor:"9,keyasint,omitzero"` // [sent bytes, recv bytes]
|
||||||
Health DockerHealth `json:"-" cbor:"5,keyasint"`
|
|
||||||
Status string `json:"-" cbor:"6,keyasint"`
|
|
||||||
Id string `json:"-" cbor:"7,keyasint"`
|
|
||||||
Image string `json:"-" cbor:"8,keyasint"`
|
|
||||||
MaxCPU uint64 `json:"-" cbor:"10,keyasint,omitzero"` // PVE: max vCPU count
|
|
||||||
MaxMem uint64 `json:"-" cbor:"11,keyasint,omitzero"` // PVE: max memory bytes
|
|
||||||
Uptime uint64 `json:"-" cbor:"12,keyasint,omitzero"` // PVE: uptime in seconds
|
|
||||||
// PrevCpu [2]uint64 `json:"-"`
|
|
||||||
CpuSystem uint64 `json:"-"`
|
|
||||||
CpuContainer uint64 `json:"-"`
|
|
||||||
PrevNet prevNetStats `json:"-"`
|
PrevNet prevNetStats `json:"-"`
|
||||||
PrevReadTime time.Time `json:"-"`
|
PrevReadTime time.Time `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stats holds data specific to docker containers for the containers table
|
||||||
|
type Stats struct {
|
||||||
|
SharedCoreMetrics // used to populate stats field in container_stats
|
||||||
|
|
||||||
|
// fields used for containers table
|
||||||
|
|
||||||
|
Health DockerHealth `json:"-" cbor:"5,keyasint"`
|
||||||
|
Status string `json:"-" cbor:"6,keyasint"`
|
||||||
|
Image string `json:"-" cbor:"8,keyasint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PveNodeStats holds data specific to PVE nodes for the pve_vms table
|
||||||
|
type PveNodeStats struct {
|
||||||
|
SharedCoreMetrics // used to populate stats field in pve_stats
|
||||||
|
|
||||||
|
// fields used for pve_vms table
|
||||||
|
|
||||||
|
MaxCPU uint64 `json:"-" cbor:"10,keyasint,omitzero"` // PVE: max vCPU count
|
||||||
|
MaxMem uint64 `json:"-" cbor:"11,keyasint,omitzero"` // PVE: max memory bytes
|
||||||
|
Uptime uint64 `json:"-" cbor:"12,keyasint,omitzero"` // PVE: uptime in seconds
|
||||||
|
Type string `json:"-" cbor:"13,keyasint,omitzero"` // PVE: resource type (e.g. "qemu" or "lxc")
|
||||||
|
}
|
||||||
|
|||||||
@@ -170,10 +170,10 @@ type Details struct {
|
|||||||
|
|
||||||
// Final data structure to return to the hub
|
// Final data structure to return to the hub
|
||||||
type CombinedData struct {
|
type CombinedData struct {
|
||||||
Stats Stats `json:"stats" cbor:"0,keyasint"`
|
Stats Stats `json:"stats" cbor:"0,keyasint"`
|
||||||
Info Info `json:"info" cbor:"1,keyasint"`
|
Info Info `json:"info" cbor:"1,keyasint"`
|
||||||
Containers []*container.Stats `json:"container" cbor:"2,keyasint"`
|
Containers []*container.Stats `json:"container" cbor:"2,keyasint"`
|
||||||
SystemdServices []*systemd.Service `json:"systemd,omitempty" cbor:"3,keyasint,omitempty"`
|
SystemdServices []*systemd.Service `json:"systemd,omitempty" cbor:"3,keyasint,omitempty"`
|
||||||
Details *Details `cbor:"4,keyasint,omitempty"`
|
Details *Details `cbor:"4,keyasint,omitempty"`
|
||||||
PVEStats []*container.Stats `json:"pve,omitempty" cbor:"5,keyasint,omitempty"`
|
PVEStats []*container.PveNodeStats `json:"pve,omitempty" cbor:"5,keyasint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createPVEVMRecords creates or updates pve_vms records
|
// createPVEVMRecords creates or updates pve_vms records
|
||||||
func createPVEVMRecords(app core.App, data []*container.Stats, systemId string) error {
|
func createPVEVMRecords(app core.App, data []*container.PveNodeStats, systemId string) error {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -370,7 +370,7 @@ func createPVEVMRecords(app core.App, data []*container.Stats, systemId string)
|
|||||||
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:type%[1]s}, {:cpu%[1]s}, {:mem%[1]s}, {:net%[1]s}, {:maxcpu%[1]s}, {:maxmem%[1]s}, {:uptime%[1]s}, {:updated})", suffix))
|
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:type%[1]s}, {:cpu%[1]s}, {:mem%[1]s}, {:net%[1]s}, {:maxcpu%[1]s}, {:maxmem%[1]s}, {:uptime%[1]s}, {:updated})", suffix))
|
||||||
params["id"+suffix] = makeStableHashId(systemId, vm.Id)
|
params["id"+suffix] = makeStableHashId(systemId, vm.Id)
|
||||||
params["name"+suffix] = vm.Name
|
params["name"+suffix] = vm.Name
|
||||||
params["type"+suffix] = vm.Image // "qemu" or "lxc"
|
params["type"+suffix] = vm.Type // "qemu" or "lxc"
|
||||||
params["cpu"+suffix] = vm.Cpu
|
params["cpu"+suffix] = vm.Cpu
|
||||||
params["mem"+suffix] = vm.Mem
|
params["mem"+suffix] = vm.Mem
|
||||||
params["maxcpu"+suffix] = vm.MaxCPU
|
params["maxcpu"+suffix] = vm.MaxCPU
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ type StatsRecord struct {
|
|||||||
// global variables for reusing allocations
|
// global variables for reusing allocations
|
||||||
var (
|
var (
|
||||||
statsRecord StatsRecord
|
statsRecord StatsRecord
|
||||||
containerStats []container.Stats
|
containerStats []container.SharedCoreMetrics
|
||||||
sumStats system.Stats
|
sumStats system.Stats
|
||||||
tempStats system.Stats
|
tempStats system.Stats
|
||||||
queryParams = make(dbx.Params, 1)
|
queryParams = make(dbx.Params, 1)
|
||||||
containerSums = make(map[string]*container.Stats)
|
containerSums = make(map[string]*container.SharedCoreMetrics)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create longer records by averaging shorter records
|
// Create longer records by averaging shorter records
|
||||||
@@ -441,7 +441,7 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the average stats of a list of container_stats or pve_stats records
|
// Calculate the average stats of a list of container_stats or pve_stats records
|
||||||
func (rm *RecordManager) AverageContainerStats(db dbx.Builder, records RecordIds, collectionName string) []container.Stats {
|
func (rm *RecordManager) AverageContainerStats(db dbx.Builder, records RecordIds, collectionName string) []container.SharedCoreMetrics {
|
||||||
// Clear global map for reuse
|
// Clear global map for reuse
|
||||||
for k := range containerSums {
|
for k := range containerSums {
|
||||||
delete(containerSums, k)
|
delete(containerSums, k)
|
||||||
@@ -461,12 +461,12 @@ func (rm *RecordManager) AverageContainerStats(db dbx.Builder, records RecordIds
|
|||||||
db.NewQuery(fmt.Sprintf("SELECT stats FROM %s WHERE id = {:id}", collectionName)).Bind(queryParams).One(&statsRecord)
|
db.NewQuery(fmt.Sprintf("SELECT stats FROM %s WHERE id = {:id}", collectionName)).Bind(queryParams).One(&statsRecord)
|
||||||
|
|
||||||
if err := json.Unmarshal(statsRecord.Stats, &containerStats); err != nil {
|
if err := json.Unmarshal(statsRecord.Stats, &containerStats); err != nil {
|
||||||
return []container.Stats{}
|
return []container.SharedCoreMetrics{}
|
||||||
}
|
}
|
||||||
for i := range containerStats {
|
for i := range containerStats {
|
||||||
stat := containerStats[i]
|
stat := containerStats[i]
|
||||||
if _, ok := sums[stat.Name]; !ok {
|
if _, ok := sums[stat.Name]; !ok {
|
||||||
sums[stat.Name] = &container.Stats{Name: stat.Name}
|
sums[stat.Name] = &container.SharedCoreMetrics{Name: stat.Name}
|
||||||
}
|
}
|
||||||
sums[stat.Name].Cpu += stat.Cpu
|
sums[stat.Name].Cpu += stat.Cpu
|
||||||
sums[stat.Name].Mem += stat.Mem
|
sums[stat.Name].Mem += stat.Mem
|
||||||
@@ -481,9 +481,9 @@ func (rm *RecordManager) AverageContainerStats(db dbx.Builder, records RecordIds
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]container.Stats, 0, len(sums))
|
result := make([]container.SharedCoreMetrics, 0, len(sums))
|
||||||
for _, value := range sums {
|
for _, value := range sums {
|
||||||
result = append(result, container.Stats{
|
result = append(result, container.SharedCoreMetrics{
|
||||||
Name: value.Name,
|
Name: value.Name,
|
||||||
Cpu: twoDecimals(value.Cpu / count),
|
Cpu: twoDecimals(value.Cpu / count),
|
||||||
Mem: twoDecimals(value.Mem / count),
|
Mem: twoDecimals(value.Mem / count),
|
||||||
|
|||||||
Reference in New Issue
Block a user