sys-exporter/collector/systemd/systemd.go

139 lines
3.3 KiB
Go
Raw Permalink Normal View History

2024-12-01 17:12:19 +00:00
package systemd
import (
"context"
"encoding/json"
"fmt"
"os/exec"
"strings"
"time"
"git.faercol.me/monitoring/sys-exporter/registry"
"github.com/prometheus/client_golang/prometheus"
2024-12-02 17:37:19 +00:00
"go.uber.org/zap"
2024-12-01 17:12:19 +00:00
)
type serviceStatus struct {
Unit string `json:"unit"`
Load string `json:"load"`
Active string `json:"active"`
Sub string `json:"sub"`
Description string `json:"description"`
}
func (s serviceStatus) unitType() string {
split := strings.Split(s.Unit, ".")
if len(split) < 2 {
return ""
}
return split[len(split)-1]
}
type SystemdCollector struct {
promExporter *prometheus.GaugeVec
updateFreq time.Duration
2024-12-02 17:37:19 +00:00
l *zap.SugaredLogger
2024-12-01 17:12:19 +00:00
}
func (c *SystemdCollector) Collect() interface{} {
return nil
}
func (c *SystemdCollector) getServicesStatus() ([]serviceStatus, error) {
2024-12-07 17:13:41 +00:00
out, err := exec.Command("/usr/bin/systemctl", "list-units", "--output", "json").CombinedOutput()
2024-12-01 17:12:19 +00:00
if err != nil {
return nil, fmt.Errorf("failed to run systemd command: %w", err)
}
res := []serviceStatus{}
if err := json.Unmarshal(out, &res); err != nil {
return nil, fmt.Errorf("failed to parse systemd output: %w", err)
}
return res, nil
}
func (c *SystemdCollector) update() error {
systemd_output, err := c.getServicesStatus()
if err != nil {
return fmt.Errorf("failed to get services status: %w", err)
}
mappingPerType := map[string][]serviceStatus{}
for _, s := range systemd_output {
mappingPerType[s.unitType()] = append(mappingPerType[s.unitType()], s)
}
totalUnits := 0
runningUnits := 0
failedUnits := 0
for unitType, units := range mappingPerType {
unitsForType := 0
healthyForType := 0
failedForType := 0
for _, u := range units {
unitsForType++
totalUnits++
if u.Active == "active" {
runningUnits++
healthyForType++
}
if u.Active == "failed" {
failedUnits++
failedForType++
}
}
c.promExporter.WithLabelValues(unitType, "total").Set(float64(unitsForType))
c.promExporter.WithLabelValues(unitType, "running").Set(float64(healthyForType))
c.promExporter.WithLabelValues(unitType, "failed").Set(float64(failedForType))
}
c.promExporter.WithLabelValues("total", "total").Set(float64(totalUnits))
c.promExporter.WithLabelValues("total", "running").Set(float64(runningUnits))
c.promExporter.WithLabelValues("total", "failed").Set(float64(failedUnits))
return nil
}
2024-12-02 17:37:19 +00:00
func (c *SystemdCollector) Run(ctx context.Context, l *zap.SugaredLogger) {
c.l = l
c.l.Debug("Initializing collector")
2024-12-01 17:12:19 +00:00
if err := c.update(); err != nil {
2024-12-02 17:37:19 +00:00
c.l.Errorf("Failed to init collector: %s\n", err)
2024-12-01 17:12:19 +00:00
}
for {
select {
case <-ctx.Done():
2024-12-02 17:37:19 +00:00
c.l.Debug("Stopping collector")
2024-12-01 17:12:19 +00:00
return
case <-time.After(c.updateFreq):
2024-12-02 17:37:19 +00:00
c.l.Debug("Updating collector")
2024-12-01 17:12:19 +00:00
if err := c.update(); err != nil {
2024-12-02 17:37:19 +00:00
c.l.Errorf("Failed to update collector: %s\n", err)
2024-12-01 17:12:19 +00:00
}
}
}
}
func (c *SystemdCollector) PromCollector() prometheus.Collector {
return c.promExporter
}
func New() *SystemdCollector {
return &SystemdCollector{
updateFreq: 30 * time.Second,
promExporter: prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "services", Subsystem: "systemd", Name: "running_units", Help: "Count of running services for systemd"}, []string{"type", "status"}),
}
}
func init() {
registry.R.MustRegisterCollector("services.systemd", New())
}