2024-12-07 17:02:15 +00:00
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 ) )
}
2024-12-07 17:13:41 +00:00
// remove the unstable interface warning
c . installedPackages = len ( strings . Split ( strings . TrimSpace ( string ( out ) ) , "\n" ) ) - 3
2024-12-07 17:02:15 +00:00
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 ) )
}
2024-12-07 17:13:41 +00:00
// remove the unstable interface warning
c . pendingUpdates = len ( strings . Split ( strings . TrimSpace ( string ( out ) ) , "\n" ) ) - 3
2024-12-07 17:02:15 +00:00
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 ( ) )
}