diff --git a/cmd/server/main.go b/cmd/server/main.go index 7a4da62..dc0e281 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -11,6 +11,7 @@ import ( "git.faercol.me/monitoring/sys-exporter/registry" "github.com/prometheus/client_golang/prometheus/promhttp" + _ "git.faercol.me/monitoring/sys-exporter/collector/apt" _ "git.faercol.me/monitoring/sys-exporter/collector/loadavg" _ "git.faercol.me/monitoring/sys-exporter/collector/meminfo" _ "git.faercol.me/monitoring/sys-exporter/collector/pacman" diff --git a/collector/apt/apt.go b/collector/apt/apt.go new file mode 100644 index 0000000..40d4ac9 --- /dev/null +++ b/collector/apt/apt.go @@ -0,0 +1,105 @@ +package apt + +import ( + "context" + "fmt" + "os/exec" + "strings" + "time" + + "git.faercol.me/monitoring/sys-exporter/registry" + "github.com/prometheus/client_golang/prometheus" + "go.uber.org/zap" +) + +type AptCollector struct { + installedPackages int + pendingUpdates int + + promExporter *prometheus.GaugeVec + updateFreq time.Duration + + l *zap.SugaredLogger +} + +func (c *AptCollector) Collect() interface{} { + return nil +} + +func (c *AptCollector) updateInstalledPackages() error { + cmd := exec.Command("/usr/bin/apt", "list", "--installed") + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run apt list: %w (%s)", err, string(out)) + } + + c.installedPackages = len(strings.Split(strings.TrimSpace(string(out)), "\n")) + return nil +} + +func (c *AptCollector) updatePendingUpdates() error { + updateCmd := exec.Command("/usr/bin/apt", "update") + out, err := updateCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run apt update: %w (%s)", err, string(out)) + } + + listCmd := exec.Command("/usr/bin/apt", "list", "--upgradable") + out, err = listCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run apt list: %w (%s)", err, string(out)) + } + + c.pendingUpdates = len(strings.Split(strings.TrimSpace(string(out)), "\n")) + return nil +} + +func (c *AptCollector) update() error { + if err := c.updateInstalledPackages(); err != nil { + return fmt.Errorf("failed to update count of installed packages: %w", err) + } + if err := c.updatePendingUpdates(); err != nil { + return fmt.Errorf("failed to update count of pending updates: %w", err) + } + + c.promExporter.WithLabelValues("total").Set(float64(c.installedPackages)) + c.promExporter.WithLabelValues("updates").Set(float64(c.pendingUpdates)) + return nil +} + +func (c *AptCollector) 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 *AptCollector) PromCollector() prometheus.Collector { + return c.promExporter +} + +func New() *AptCollector { + return &AptCollector{ + updateFreq: 30 * time.Minute, + promExporter: prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "packages", Subsystem: "apt", Name: "packages_count", Help: "Count of apt packages"}, []string{"status"}), + } +} + +func init() { + registry.R.MustRegisterCollector("packages.apt", New()) +} diff --git a/debian/sys-exporter/etc/sys-exporter/config.yml b/debian/sys-exporter/etc/sys-exporter/config.yml index d96019e..5555a38 100644 --- a/debian/sys-exporter/etc/sys-exporter/config.yml +++ b/debian/sys-exporter/etc/sys-exporter/config.yml @@ -6,3 +6,4 @@ collectors: - services.systemd - system.uptime - system.info + - packages.apt