sys-exporter/collector/systemd/systemd.go

138 lines
3.3 KiB
Go

package systemd
import (
"context"
"encoding/json"
"fmt"
"os/exec"
"strings"
"time"
"git.faercol.me/monitoring/sys-exporter/registry"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
)
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
l *zap.SugaredLogger
}
func (c *SystemdCollector) Collect() interface{} {
return nil
}
func (c *SystemdCollector) getServicesStatus() ([]serviceStatus, error) {
out, err := exec.Command("/sbin/systemctl", "list-units", "--output", "json").CombinedOutput()
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
}
func (c *SystemdCollector) Run(ctx context.Context, l *zap.SugaredLogger) {
c.l = l
c.l.Debug("Initializing collector")
if err := c.update(); err != nil {
c.l.Errorf("Failed to init collector: %s\n", err)
}
for {
select {
case <-ctx.Done():
c.l.Debug("Stopping collector")
return
case <-time.After(c.updateFreq):
c.l.Debug("Updating collector")
if err := c.update(); err != nil {
c.l.Errorf("Failed to update collector: %s\n", err)
}
}
}
}
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())
}