- agent-linux/: Kompletter Linux/Proxmox RMM Agent - Collectors: CPU, Memory, Disk, Network, Services, Updates, Proxmox (VMs, Container, Storage, ZFS), Backups - Backup Collector: Jobs nach Node gefiltert (Cluster-aware) - WebSocket: Tunnel-Support, writeMux fuer stabile Verbindungen - Systemd Service Template - updater/: Plattform-unabhaengiger Updater (FreeBSD + Linux) - runtime.GOOS erkennt Plattform automatisch - FreeBSD: /usr/local/rmm/, service rmm_agent - Linux: /usr/local/bin/, /etc/rmm/, systemctl rmm-agent - Firmware-Download mit ?platform= Parameter - frontend/: Proxmox-Bereich - ProxmoxServers.jsx: Eigene Seite fuer PVE Server mit VM/CT Counts - ProxmoxPanel.jsx: Detail-Panel mit Uebersicht, VMs, Container, Storage, ZFS, Tunnel, Dienste, Updates, Backups - Agents.jsx: Filtert Linux-Agents raus (nur OPNsense) - Sidebar: Proxmox Navigationspunkt - Installationsanleitung mit rmm-agent-linux + rmm-updater-linux - backend: Platform-Feld fuer Agents, Firmware multi-platform
168 lines
3.2 KiB
Go
168 lines
3.2 KiB
Go
package collector
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type CPUInfo struct {
|
|
Model string
|
|
Cores int
|
|
Threads int
|
|
FreqMHz int
|
|
UsagePercent float64
|
|
}
|
|
|
|
func CollectCPU() CPUInfo {
|
|
cpu := CPUInfo{}
|
|
|
|
// CPU-Informationen aus /proc/cpuinfo lesen
|
|
if data, err := os.ReadFile("/proc/cpuinfo"); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
cpuCount := 0
|
|
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
parts := strings.SplitN(line, ":", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
key := strings.TrimSpace(parts[0])
|
|
value := strings.TrimSpace(parts[1])
|
|
|
|
switch key {
|
|
case "model name":
|
|
if cpu.Model == "" { // Nur vom ersten CPU nehmen
|
|
cpu.Model = value
|
|
}
|
|
case "cpu MHz":
|
|
if cpu.FreqMHz == 0 { // Nur vom ersten CPU nehmen
|
|
if freq, err := strconv.ParseFloat(value, 64); err == nil {
|
|
cpu.FreqMHz = int(freq)
|
|
}
|
|
}
|
|
case "processor":
|
|
cpuCount++
|
|
case "cpu cores":
|
|
if cores, err := strconv.Atoi(value); err == nil && cpu.Cores == 0 {
|
|
cpu.Cores = cores
|
|
}
|
|
case "siblings":
|
|
if threads, err := strconv.Atoi(value); err == nil && cpu.Threads == 0 {
|
|
cpu.Threads = threads
|
|
}
|
|
}
|
|
}
|
|
|
|
// Falls cpu cores und siblings nicht vorhanden, nutze processor count
|
|
if cpu.Cores == 0 {
|
|
cpu.Cores = cpuCount
|
|
}
|
|
if cpu.Threads == 0 {
|
|
cpu.Threads = cpuCount
|
|
}
|
|
}
|
|
|
|
// CPU-Usage berechnen
|
|
cpu.UsagePercent = calculateCPUUsage()
|
|
|
|
return cpu
|
|
}
|
|
|
|
// Berechnet die aktuelle CPU-Auslastung über zwei /proc/stat Messungen
|
|
func calculateCPUUsage() float64 {
|
|
stat1 := readCPUStat()
|
|
if stat1 == nil {
|
|
return 0.0
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond) // Kurz warten für zweite Messung
|
|
|
|
stat2 := readCPUStat()
|
|
if stat2 == nil {
|
|
return 0.0
|
|
}
|
|
|
|
// Delta berechnen
|
|
totalDelta := (stat2.total - stat1.total)
|
|
idleDelta := (stat2.idle - stat1.idle)
|
|
|
|
if totalDelta <= 0 {
|
|
return 0.0
|
|
}
|
|
|
|
usage := 100.0 * float64(totalDelta-idleDelta) / float64(totalDelta)
|
|
if usage < 0 {
|
|
usage = 0
|
|
}
|
|
if usage > 100 {
|
|
usage = 100
|
|
}
|
|
|
|
return usage
|
|
}
|
|
|
|
type cpuStat struct {
|
|
total int64
|
|
idle int64
|
|
}
|
|
|
|
// Liest CPU-Statistiken aus /proc/stat
|
|
func readCPUStat() *cpuStat {
|
|
data, err := os.ReadFile("/proc/stat")
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
lines := strings.Split(string(data), "\n")
|
|
if len(lines) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Erste Zeile sollte "cpu ..." sein
|
|
firstLine := strings.TrimSpace(lines[0])
|
|
if !strings.HasPrefix(firstLine, "cpu ") {
|
|
return nil
|
|
}
|
|
|
|
fields := strings.Fields(firstLine)
|
|
if len(fields) < 5 {
|
|
return nil
|
|
}
|
|
|
|
// cpu user nice system idle iowait irq softirq ...
|
|
var values []int64
|
|
for i := 1; i < len(fields) && i <= 7; i++ { // max 7 Werte lesen
|
|
if val, err := strconv.ParseInt(fields[i], 10, 64); err == nil {
|
|
values = append(values, val)
|
|
}
|
|
}
|
|
|
|
if len(values) < 4 {
|
|
return nil
|
|
}
|
|
|
|
// Total = Summe aller Werte
|
|
total := int64(0)
|
|
for _, val := range values {
|
|
total += val
|
|
}
|
|
|
|
// Idle = idle + iowait (falls iowait verfügbar)
|
|
idle := values[3] // idle
|
|
if len(values) > 4 {
|
|
idle += values[4] // + iowait
|
|
}
|
|
|
|
return &cpuStat{
|
|
total: total,
|
|
idle: idle,
|
|
}
|
|
} |