sys-exporter/collector/pacman/pacman.go

120 lines
3 KiB
Go
Raw Permalink Normal View History

2024-12-01 16:00:52 +00:00
package pacman
import (
"context"
"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 16:00:52 +00:00
)
const (
totalLabel = "total"
updatesLabel = "updates"
)
func listCommandLines(command string, args ...string) (int, error) {
cmd := exec.Command(command, args...)
out, err := cmd.CombinedOutput()
if err != nil {
2024-12-01 17:12:19 +00:00
if string(out) == "" { // pacman can return a returncode 1 when the list is empty
return 0, nil
}
2024-12-01 16:00:52 +00:00
return 0, fmt.Errorf("failed to run command: %w (%s)", err, string(out))
}
return len(strings.Split(string(out), "\n")), nil
}
type PacmanCollector struct {
installedPackages int
promPackages *prometheus.GaugeVec
pendingUpdates int
updateFreq time.Duration
2024-12-02 17:37:19 +00:00
l *zap.SugaredLogger
2024-12-01 16:00:52 +00:00
}
func (c *PacmanCollector) Collect() interface{} {
return map[string]int{
"installed_packages": c.installedPackages,
"pending_updates": c.pendingUpdates,
}
}
func (c *PacmanCollector) updateInstalledPackages() error {
count, err := listCommandLines("/sbin/pacman", "-Q")
if err != nil {
return fmt.Errorf("failed to count installed packages: %w", err)
}
c.installedPackages = count
c.promPackages.WithLabelValues(totalLabel).Set(float64(count))
return nil
}
func (c *PacmanCollector) updatePendingUpdates() error {
updateCmd := exec.Command("/sbin/pacman", "-Sy")
if out, err := updateCmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to update pacman cache: %w (%s)", err, string(out))
}
count, err := listCommandLines("/sbin/pacman", "-Qu")
if err != nil {
return fmt.Errorf("failed to count updated packages: %w", err)
}
c.pendingUpdates = count
c.promPackages.WithLabelValues(updatesLabel).Set(float64(count))
return nil
}
2024-12-02 17:37:19 +00:00
func (c *PacmanCollector) Run(ctx context.Context, l *zap.SugaredLogger) {
c.l = l
c.l.Debug("Initializing collector")
2024-12-01 16:00:52 +00:00
if err := c.updateInstalledPackages(); err != nil {
2024-12-02 17:37:19 +00:00
c.l.Errorf("Failed to init count of installed packages: %s\n", err)
2024-12-01 16:00:52 +00:00
}
if err := c.updatePendingUpdates(); err != nil {
2024-12-02 17:37:19 +00:00
c.l.Errorf("Failed to init count of updates: %s\n", err)
2024-12-01 16:00:52 +00:00
}
for {
select {
case <-ctx.Done():
2024-12-02 17:37:19 +00:00
c.l.Debug("Stopping collector")
2024-12-01 16:00:52 +00:00
return
case <-time.After(c.updateFreq):
2024-12-02 17:37:19 +00:00
c.l.Debug("Updating collector")
2024-12-01 16:00:52 +00:00
if err := c.updateInstalledPackages(); err != nil {
2024-12-02 17:37:19 +00:00
c.l.Errorf("Failed to update count of installed packages: %s\n", err)
2024-12-01 16:00:52 +00:00
}
if err := c.updatePendingUpdates(); err != nil {
2024-12-02 17:37:19 +00:00
c.l.Errorf("Failed to update count of updates: %s\n", err)
2024-12-01 16:00:52 +00:00
}
}
}
}
func (c *PacmanCollector) PromCollector() prometheus.Collector {
return c.promPackages
}
func New() *PacmanCollector {
c := PacmanCollector{
updateFreq: 5 * time.Minute,
promPackages: prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "packages", Subsystem: "pacman", Name: "packages_count", Help: "Count of pacman packages"}, []string{"status"}),
}
return &c
}
func init() {
registry.R.MustRegisterCollector("packages.pacman", New())
}