cynfo3000 7f9fbd257f Linux/Proxmox Agent + Plattform-Updater + Proxmox Frontend
- 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
2026-03-01 22:03:18 +01:00

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,
}
}