package collector import ( "context" "fmt" "log" "strconv" "strings" "time" "gitea.sinav-lab.com/sinav/keenetic-exporter-v2/internal/device" "gitea.sinav-lab.com/sinav/keenetic-exporter-v2/internal/keenetic" "gitea.sinav-lab.com/sinav/keenetic-exporter-v2/internal/utils" "github.com/prometheus/client_golang/prometheus" ) type ProcessCollector struct { cpuDesc *prometheus.Desc vmSizeDesc *prometheus.Desc vmRSSDesc *prometheus.Desc threadsDesc *prometheus.Desc fdsDesc *prometheus.Desc } func NewProcessCollector() *ProcessCollector { labels := []string{"device", "comm", "pid"} return &ProcessCollector{ cpuDesc: prometheus.NewDesc("keenetic_process_cpu_seconds", "CPU usage of the process", labels, nil), vmSizeDesc: prometheus.NewDesc("keenetic_process_memory_virtual_bytes", "Virtual memory size in bytes", labels, nil), vmRSSDesc: prometheus.NewDesc("keenetic_process_memory_resident_bytes", "Resident memory size in bytes", labels, nil), threadsDesc: prometheus.NewDesc("keenetic_process_threads", "Number of threads", labels, nil), fdsDesc: prometheus.NewDesc("keenetic_process_fds", "Number of open file descriptors", labels, nil), } } func (c *ProcessCollector) Name() string { return "process" } func (c *ProcessCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.cpuDesc ch <- c.vmSizeDesc ch <- c.vmRSSDesc ch <- c.threadsDesc ch <- c.fdsDesc } func (c *ProcessCollector) Collect(dev *device.Device, ch chan<- prometheus.Metric) error { var processInfo any var ok bool hostname := dev.Name client := dev.Client dev.CacheMutex.RLock() processInfo, ok = dev.Cache["process"] 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.GetProcessInfo(ctx) cancel() if err != nil { log.Printf("failed to get process info from %s: %v", hostname, err) return err } processInfo = fresh dev.CacheMutex.Lock() dev.Cache["process"] = fresh dev.LastUpdate = time.Now() dev.CacheMutex.Unlock() } procs, ok := processInfo.([]*keenetic.ProcessInfo) if !ok { log.Printf("invalid cache data type for process info on %s", hostname) return fmt.Errorf("invalid cache data type for process info") } for _, p := range procs { labels := []string{hostname, p.Comm, p.Pid} vmSize := utils.ParseKB(p.VMSize) * 1024 vmRSS := utils.ParseKB(p.VMRSS) * 1024 threads, _ := strconv.Atoi(strings.TrimSpace(p.Threads)) ch <- prometheus.MustNewConstMetric(c.cpuDesc, prometheus.CounterValue, p.Statistics.CPU.Cur, labels...) ch <- prometheus.MustNewConstMetric(c.vmSizeDesc, prometheus.GaugeValue, vmSize, labels...) ch <- prometheus.MustNewConstMetric(c.vmRSSDesc, prometheus.GaugeValue, vmRSS, labels...) ch <- prometheus.MustNewConstMetric(c.threadsDesc, prometheus.GaugeValue, float64(threads), labels...) ch <- prometheus.MustNewConstMetric(c.fdsDesc, prometheus.GaugeValue, p.Fds, labels...) } return nil }