mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-24 06:26:17 +01:00
[Feature] Basic systemd service monitoring (#1153)
* basic systemd service monitoring * update to work after /internal rename * monitor systemd service cpu and memory usage --------- Co-authored-by: henrygd <hank@henrygd.me>
This commit is contained in:
@@ -31,6 +31,7 @@ type Agent struct {
|
||||
netInterfaces map[string]struct{} // Stores all valid network interfaces
|
||||
netIoStats system.NetIoStats // Keeps track of bandwidth usage
|
||||
dockerManager *dockerManager // Manages Docker API requests
|
||||
systemdManager *systemdManager // Manages systemd services
|
||||
sensorConfig *SensorConfig // Sensors config
|
||||
systemInfo system.Info // Host system info
|
||||
gpuManager *GPUManager // Manages GPU data
|
||||
@@ -88,6 +89,13 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) {
|
||||
// initialize docker manager
|
||||
agent.dockerManager = newDockerManager(agent)
|
||||
|
||||
// initialize systemd manager
|
||||
if sm, err := newSystemdManager(); err != nil {
|
||||
slog.Debug("Systemd", "err", err)
|
||||
} else {
|
||||
agent.systemdManager = sm
|
||||
}
|
||||
|
||||
// initialize GPU manager
|
||||
if gm, err := NewGPUManager(); err != nil {
|
||||
slog.Debug("GPU", "err", err)
|
||||
@@ -137,6 +145,11 @@ func (a *Agent) gatherStats(sessionID string) *system.CombinedData {
|
||||
}
|
||||
}
|
||||
|
||||
if a.systemdManager != nil {
|
||||
data.SystemdServices = a.systemdManager.getServiceStats()
|
||||
slog.Debug("Systemd services", "data", data.SystemdServices)
|
||||
}
|
||||
|
||||
data.Stats.ExtraFs = make(map[string]*system.FsStats)
|
||||
for name, stats := range a.fsStats {
|
||||
if !stats.Root && stats.DiskTotal > 0 {
|
||||
|
||||
107
agent/systemd.go
Normal file
107
agent/systemd.go
Normal file
@@ -0,0 +1,107 @@
|
||||
//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
|
||||
}
|
||||
18
agent/systemd_unsupported.go
Normal file
18
agent/systemd_unsupported.go
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build !linux
|
||||
|
||||
package agent
|
||||
|
||||
import "github.com/henrygd/beszel/internal/entities/systemd"
|
||||
|
||||
// systemdManager manages the collection of systemd service statistics.
|
||||
type systemdManager struct{}
|
||||
|
||||
// newSystemdManager creates a new systemdManager.
|
||||
func newSystemdManager() (*systemdManager, error) {
|
||||
return &systemdManager{}, nil
|
||||
}
|
||||
|
||||
// getServiceStats returns nil for non-linux systems.
|
||||
func (sm *systemdManager) getServiceStats() []*systemd.Service {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user