package collector import ( "context" "fmt" "log" "strconv" "time" "gitea.sinav-lab.com/sinav/keenetic-exporter-v2/internal/device" "gitea.sinav-lab.com/sinav/keenetic-exporter-v2/internal/keenetic" "github.com/prometheus/client_golang/prometheus" ) type SystemCollector struct { cpuLoadDesc *prometheus.Desc memTotalDesc *prometheus.Desc memFreeDesc *prometheus.Desc memCacheDesc *prometheus.Desc memBuffersDesc *prometheus.Desc swapTotalDesc *prometheus.Desc swapFreeDesc *prometheus.Desc connTotalDesc *prometheus.Desc connFreeDesc *prometheus.Desc uptimeDesc *prometheus.Desc } func NewSystemCollector() *SystemCollector { labels := []string{"device"} return &SystemCollector{ cpuLoadDesc: prometheus.NewDesc("keenetic_cpu_load", "Current CPU load", labels, nil), memTotalDesc: prometheus.NewDesc("keenetic_memory_total_bytes", "Total memory in bytes", labels, nil), memFreeDesc: prometheus.NewDesc("keenetic_memory_free_bytes", "Free memory in bytes", labels, nil), memCacheDesc: prometheus.NewDesc("keenetic_memory_cache_bytes", "Cache memory in bytes", labels, nil), memBuffersDesc: prometheus.NewDesc("keenetic_memory_buffers_bytes", "Buffer memory in bytes", labels, nil), swapTotalDesc: prometheus.NewDesc("keenetic_swap_total_bytes", "Total swap in bytes", labels, nil), swapFreeDesc: prometheus.NewDesc("keenetic_swap_free_bytes", "Free swap in bytes", labels, nil), connTotalDesc: prometheus.NewDesc("keenetic_connections_total", "Total number of connections", labels, nil), connFreeDesc: prometheus.NewDesc("keenetic_connections_free", "Number of free connections", labels, nil), uptimeDesc: prometheus.NewDesc("keenetic_uptime_seconds", "Device uptime in seconds", labels, nil), } } func (c *SystemCollector) Name() string { return "system" } func (c *SystemCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.cpuLoadDesc ch <- c.memTotalDesc ch <- c.memFreeDesc ch <- c.memCacheDesc ch <- c.memBuffersDesc ch <- c.swapTotalDesc ch <- c.swapFreeDesc ch <- c.connTotalDesc ch <- c.connFreeDesc ch <- c.uptimeDesc } func (c *SystemCollector) Collect(dev *device.Device, ch chan<- prometheus.Metric) error { var sysInfo any var ok bool hostname := dev.Name client := dev.Client dev.CacheMutex.RLock() sysInfo, ok = dev.Cache["system"] valid := time.Since(dev.LastUpdate) < dev.CacheTTL dev.CacheMutex.RUnlock() if !ok || !valid { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) fresh, err := client.GetSystemInfo(ctx) cancel() if err != nil { log.Printf("Failed to get system info from %s: %v", hostname, err) return err } sysInfo = fresh dev.CacheMutex.Lock() dev.Cache["system"] = fresh dev.LastUpdate = time.Now() dev.CacheMutex.Unlock() } s, ok := sysInfo.(*keenetic.SystemInfo) if !ok || s == nil { log.Printf("invalid cache data type for system info on %s", hostname) return fmt.Errorf("invalid cache data type for system info") } uptime, err := strconv.ParseFloat(s.Uptime, 64) if err != nil { log.Printf("failed to parse uptime for %s: %v", hostname, err) return fmt.Errorf("failed to parse uptime: %w", err) } ch <- prometheus.MustNewConstMetric(c.cpuLoadDesc, prometheus.GaugeValue, s.CpuLoad, hostname) ch <- prometheus.MustNewConstMetric(c.memTotalDesc, prometheus.GaugeValue, s.MemTotal, hostname) ch <- prometheus.MustNewConstMetric(c.memFreeDesc, prometheus.GaugeValue, s.MemFree, hostname) ch <- prometheus.MustNewConstMetric(c.memCacheDesc, prometheus.GaugeValue, s.MemCache, hostname) ch <- prometheus.MustNewConstMetric(c.memBuffersDesc, prometheus.GaugeValue, s.MemBuffers, hostname) ch <- prometheus.MustNewConstMetric(c.swapTotalDesc, prometheus.GaugeValue, s.SwapTotal, hostname) ch <- prometheus.MustNewConstMetric(c.swapFreeDesc, prometheus.GaugeValue, s.SwapFree, hostname) ch <- prometheus.MustNewConstMetric(c.connTotalDesc, prometheus.GaugeValue, s.ConnTotal, hostname) ch <- prometheus.MustNewConstMetric(c.connFreeDesc, prometheus.GaugeValue, s.ConnFree, hostname) ch <- prometheus.MustNewConstMetric(c.uptimeDesc, prometheus.CounterValue, uptime, hostname) return nil }