commit for version used in evaluation of thesis

This commit is contained in:
Patryk Hegenberg 2026-03-29 10:03:18 +02:00
commit 72635dc7b9
27 changed files with 6084 additions and 0 deletions

View file

@ -0,0 +1,140 @@
package collector
import (
"bytes"
"context"
"log"
"os/exec"
"strings"
"sync"
"time"
"codeberg.org/pata1704/guenther/pkg/types"
)
// SystemctlCollector periodically checks the status of systemd services.
type SystemctlCollector struct {
services []string
interval time.Duration
outputChan chan<- types.ServiceStatus
healthChan chan<- types.StageHealth
wg sync.WaitGroup
mu sync.Mutex
processed uint64
}
// NewSystemctlCollector creates a new collector for the given services.
func NewSystemctlCollector(
services []string,
interval time.Duration,
output chan<- types.ServiceStatus,
health chan<- types.StageHealth,
) *SystemctlCollector {
return &SystemctlCollector{
services: services,
interval: interval,
outputChan: output,
healthChan: health,
}
}
// Start launches the collection loop.
func (c *SystemctlCollector) Start(ctx context.Context) {
if len(c.services) == 0 {
log.Println("systemctl: no services configured for monitoring")
return
}
c.wg.Go(func() {
ticker := time.NewTicker(c.interval)
reportTicker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
defer reportTicker.Stop()
// Immediate first collection.
c.collect()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
c.collect()
case <-reportTicker.C:
c.emitHealth()
}
}
})
}
// Wait waits for the collector to stop.
func (c *SystemctlCollector) Wait() {
c.wg.Wait()
}
func (c *SystemctlCollector) collect() {
for _, service := range c.services {
status, err := c.getServiceStatus(service)
if err != nil {
log.Printf("systemctl: error getting status for %s: %v", service, err)
continue
}
select {
case c.outputChan <- status:
c.mu.Lock()
c.processed++
c.mu.Unlock()
default:
log.Printf("systemctl: output channel full dropping status for %s", service)
}
}
}
func (c *SystemctlCollector) getServiceStatus(service string) (types.ServiceStatus, error) {
// Use systemctl show to get machine-readable properties.
cmd := exec.Command("systemctl", "show", "-p", "ActiveState,SubState", service)
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return types.ServiceStatus{}, err
}
lines := strings.Split(strings.TrimSpace(out.String()), "\n")
status := types.ServiceStatus{
Timestamp: time.Now(),
ServiceName: service,
}
for _, line := range lines {
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
switch parts[0] {
case "ActiveState":
status.ActiveState = parts[1]
case "SubState":
status.SubState = parts[1]
}
}
return status, nil
}
func (c *SystemctlCollector) emitHealth() {
c.mu.Lock()
count := c.processed
c.mu.Unlock()
select {
case c.healthChan <- types.StageHealth{
StageName: "systemctl_collector",
EventsProcessed: count,
LastUpdate: time.Now(),
}:
default:
}
}