Add start of sys exporter

This commit is contained in:
Melora Hugues 2024-12-01 17:00:52 +01:00
parent a99d624cdd
commit c48f1e0f66
7 changed files with 309 additions and 1 deletions

View file

@ -1,7 +1,30 @@
package main package main
import "fmt" import (
"context"
"fmt"
"net/http"
"git.faercol.me/monitoring/sys-exporter/registry"
"github.com/prometheus/client_golang/prometheus/promhttp"
_ "git.faercol.me/monitoring/sys-exporter/collector/pacman"
_ "git.faercol.me/monitoring/sys-exporter/collector/uptime"
)
// type gatherer struct {
// reg *prometheus.Registry
// }
// func (g *gatherer) Gather() (pcm.MetricFamily, error) {
// return g.reg.
// }
func main() { func main() {
fmt.Println("starting server") fmt.Println("starting server")
registry.R.Run(context.Background())
http.Handle("/metrics", promhttp.HandlerFor(registry.R.PromRegistry(), promhttp.HandlerOpts{}))
http.ListenAndServe(":2112", nil)
} }

13
collector/collector.go Normal file
View file

@ -0,0 +1,13 @@
package collector
import (
"context"
"github.com/prometheus/client_golang/prometheus"
)
type Collector interface {
Collect() interface{}
PromCollector() prometheus.Collector
Run(ctx context.Context)
}

108
collector/pacman/pacman.go Normal file
View file

@ -0,0 +1,108 @@
package pacman
import (
"context"
"fmt"
"os/exec"
"strings"
"time"
"git.faercol.me/monitoring/sys-exporter/registry"
"github.com/prometheus/client_golang/prometheus"
)
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 {
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
}
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) {
if err := c.updateInstalledPackages(); err != nil {
fmt.Printf("Failed to init count of installed packages: %s\n", err)
}
if err := c.updatePendingUpdates(); err != nil {
fmt.Printf("Failed to init count of updates: %s\n", err)
}
for {
select {
case <-ctx.Done():
return
case <-time.After(c.updateFreq):
if err := c.updateInstalledPackages(); err != nil {
fmt.Printf("Failed to update count of installed packages: %s\n", err)
}
if err := c.updatePendingUpdates(); err != nil {
fmt.Printf("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())
}

View file

@ -0,0 +1,84 @@
package uptime
import (
"context"
"fmt"
"os"
"strconv"
"strings"
"time"
"git.faercol.me/monitoring/sys-exporter/registry"
"github.com/prometheus/client_golang/prometheus"
)
const procUptimeLocation = "/proc/uptime"
type UptimeCollector struct {
ctx context.Context
procFileLocation string
uptimeFreq time.Duration
uptime time.Duration
uptimeProm prometheus.Gauge
}
func (c *UptimeCollector) Collect() interface{} {
return c.uptime
}
func (c *UptimeCollector) update() error {
fileContent, err := os.ReadFile(c.procFileLocation)
if err != nil {
return fmt.Errorf("failed to read uptime file: %w", err)
}
uptimeVals := strings.Split(string(fileContent), " ")
if len(uptimeVals) != 2 {
return fmt.Errorf("invalid format for /proc/uptime: %q", string(fileContent))
}
uptimeFloat, err := strconv.ParseFloat(uptimeVals[0], 64)
if err != nil {
return fmt.Errorf("invalid uptime format for float %s: %w", uptimeVals[0], err)
}
c.uptime = time.Duration(int(uptimeFloat)) * time.Second
c.uptimeProm.Set(uptimeFloat)
return nil
}
func (c *UptimeCollector) PromCollector() prometheus.Collector {
return c.uptimeProm
}
func (c *UptimeCollector) Run(ctx context.Context) {
if err := c.update(); err != nil {
fmt.Printf("Failed to init collector: %s\n", err)
}
for {
select {
case <-ctx.Done():
return
case <-time.After(c.uptimeFreq):
if err := c.update(); err != nil {
fmt.Printf("Failed to update collector: %s\n", err)
}
}
}
}
func New() *UptimeCollector {
return &UptimeCollector{
ctx: context.TODO(),
procFileLocation: procUptimeLocation,
uptimeFreq: 1 * time.Second,
uptime: 0,
uptimeProm: prometheus.NewGauge(prometheus.GaugeOpts{Namespace: "system", Name: "uptime_nanosec", Help: "System uptime in nanoseconds"}),
}
}
func init() {
registry.R.MustRegisterCollector("system.uptime", New())
}

14
go.mod
View file

@ -1,3 +1,17 @@
module git.faercol.me/monitoring/sys-exporter module git.faercol.me/monitoring/sys-exporter
go 1.23.3 go 1.23.3
require github.com/prometheus/client_golang v1.20.5
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
golang.org/x/sys v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)

24
go.sum Normal file
View file

@ -0,0 +1,24 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=

42
registry/registry.go Normal file
View file

@ -0,0 +1,42 @@
package registry
import (
"context"
"git.faercol.me/monitoring/sys-exporter/collector"
"github.com/prometheus/client_golang/prometheus"
)
var R CollectorRegistry
type CollectorRegistry struct {
promRegistry *prometheus.Registry
collectors map[string]collector.Collector
}
func (r *CollectorRegistry) RegisterCollector(name string, c collector.Collector) error {
r.collectors[name] = c
return r.promRegistry.Register(c.PromCollector())
}
func (r *CollectorRegistry) MustRegisterCollector(name string, c collector.Collector) {
r.collectors[name] = c
r.promRegistry.MustRegister(c.PromCollector())
}
func (r *CollectorRegistry) PromRegistry() *prometheus.Registry {
return r.promRegistry
}
func (r *CollectorRegistry) Run(ctx context.Context) {
for _, c := range r.collectors {
go c.Run(ctx)
}
}
func init() {
R = CollectorRegistry{
promRegistry: prometheus.NewRegistry(),
collectors: make(map[string]collector.Collector),
}
}