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 ( ) )
}