mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-21 21:26:16 +01:00
* basic systemd service monitoring * update to work after /internal rename * monitor systemd service cpu and memory usage --------- Co-authored-by: henrygd <hank@henrygd.me>
108 lines
2.6 KiB
Go
108 lines
2.6 KiB
Go
//go:build linux
|
|
|
|
package agent
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"math"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/coreos/go-systemd/v22/dbus"
|
|
"github.com/henrygd/beszel/internal/entities/systemd"
|
|
)
|
|
|
|
// systemdManager manages the collection of systemd service statistics.
|
|
type systemdManager struct {
|
|
conn *dbus.Conn
|
|
serviceStatsMap map[string]*systemd.Service
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// newSystemdManager creates a new systemdManager.
|
|
func newSystemdManager() (*systemdManager, error) {
|
|
conn, err := dbus.New()
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "permission denied") {
|
|
slog.Error("Permission denied when connecting to systemd. Run as root or with appropriate user permissions.", "err", err)
|
|
return nil, err
|
|
}
|
|
slog.Error("Error connecting to systemd", "err", err)
|
|
return nil, err
|
|
}
|
|
|
|
return &systemdManager{
|
|
conn: conn,
|
|
serviceStatsMap: make(map[string]*systemd.Service),
|
|
}, nil
|
|
}
|
|
|
|
// getServiceStats collects statistics for all running systemd services.
|
|
func (sm *systemdManager) getServiceStats() []*systemd.Service {
|
|
units, err := sm.conn.ListUnitsContext(context.Background())
|
|
if err != nil {
|
|
slog.Error("Error listing systemd units", "err", err)
|
|
return nil
|
|
}
|
|
|
|
var services []*systemd.Service
|
|
for _, unit := range units {
|
|
if strings.HasSuffix(unit.Name, ".service") {
|
|
service := sm.updateServiceStats(unit)
|
|
services = append(services, service)
|
|
}
|
|
}
|
|
return services
|
|
}
|
|
|
|
// updateServiceStats updates the statistics for a single systemd service.
|
|
func (sm *systemdManager) updateServiceStats(unit dbus.UnitStatus) *systemd.Service {
|
|
sm.mu.Lock()
|
|
defer sm.mu.Unlock()
|
|
|
|
props, err := sm.conn.GetUnitTypeProperties(unit.Name, "Service")
|
|
if err != nil {
|
|
slog.Debug("could not get unit type properties", "unit", unit.Name, "err", err)
|
|
return &systemd.Service{
|
|
Name: unit.Name,
|
|
Status: unit.ActiveState,
|
|
}
|
|
}
|
|
|
|
var cpuUsage uint64
|
|
if val, ok := props["CPUUsageNSec"]; ok {
|
|
if v, ok := val.(uint64); ok {
|
|
cpuUsage = v
|
|
}
|
|
}
|
|
|
|
var memUsage uint64
|
|
if val, ok := props["MemoryCurrent"]; ok {
|
|
if v, ok := val.(uint64); ok {
|
|
memUsage = v
|
|
}
|
|
}
|
|
|
|
service, exists := sm.serviceStatsMap[unit.Name]
|
|
if !exists {
|
|
service = &systemd.Service{
|
|
Name: unit.Name,
|
|
Status: unit.ActiveState,
|
|
}
|
|
sm.serviceStatsMap[unit.Name] = service
|
|
}
|
|
|
|
service.Status = unit.ActiveState
|
|
|
|
// If memUsage is MaxUint64 the api is saying it's not available, return 0
|
|
if memUsage == math.MaxUint64 {
|
|
memUsage = 0
|
|
}
|
|
|
|
service.Mem = float64(memUsage) / (1024 * 1024) // Convert to MB
|
|
service.CalculateCPUPercent(cpuUsage)
|
|
|
|
return service
|
|
}
|