119 lines
3 KiB
Go
119 lines
3 KiB
Go
package pacman
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.faercol.me/monitoring/sys-exporter/registry"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
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 {
|
|
if string(out) == "" { // pacman can return a returncode 1 when the list is empty
|
|
return 0, nil
|
|
}
|
|
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
|
|
|
|
l *zap.SugaredLogger
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (c *PacmanCollector) Run(ctx context.Context, l *zap.SugaredLogger) {
|
|
c.l = l
|
|
|
|
c.l.Debug("Initializing collector")
|
|
if err := c.updateInstalledPackages(); err != nil {
|
|
c.l.Errorf("Failed to init count of installed packages: %s\n", err)
|
|
}
|
|
if err := c.updatePendingUpdates(); err != nil {
|
|
c.l.Errorf("Failed to init count of updates: %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.updateInstalledPackages(); err != nil {
|
|
c.l.Errorf("Failed to update count of installed packages: %s\n", err)
|
|
}
|
|
if err := c.updatePendingUpdates(); err != nil {
|
|
c.l.Errorf("Failed to update count of updates: %s\n", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|