package uptime import ( "context" "fmt" "os" "strconv" "strings" "time" "git.faercol.me/monitoring/sys-exporter/registry" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" ) const procUptimeLocation = "/proc/uptime" type UptimeCollector struct { ctx context.Context procFileLocation string uptimeFreq time.Duration uptime time.Duration uptimeProm prometheus.Gauge l *zap.SugaredLogger } 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, 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.uptimeFreq): c.l.Debug("Updating collector") if err := c.update(); err != nil { c.l.Errorf("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_sec", Help: "System uptime in seconds"}), } } func init() { registry.R.MustRegisterCollector("system.uptime", New()) }