package collector import ( "context" "fmt" "log" "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 InterfaceCollector struct { linkDesc *prometheus.Desc stateDesc *prometheus.Desc mtuDesc *prometheus.Desc txQueueLengthDesc *prometheus.Desc uptimeDesc *prometheus.Desc temperatureDesc *prometheus.Desc channelDesc *prometheus.Desc } func NewInterfaceCollector() *InterfaceCollector { labels := []string{"device", "id", "name", "type", "description", "mac", "connected"} return &InterfaceCollector{ linkDesc: prometheus.NewDesc("keenetic_interface_link_up", "Link status (1=up, 0=down)", labels, nil), stateDesc: prometheus.NewDesc("keenetic_interface_state_up", "Interface state (1=up, 0=down)", labels, nil), mtuDesc: prometheus.NewDesc("keenetic_interface_mtu", "MTU size", labels, nil), txQueueLengthDesc: prometheus.NewDesc("keenetic_interface_tx_queue_length", "TX queue length", labels, nil), uptimeDesc: prometheus.NewDesc("keenetic_interface_uptime_seconds", "Interface uptime in seconds", labels, nil), temperatureDesc: prometheus.NewDesc("keenetic_interface_temperature", "Interface temperature in celsius", labels, nil), channelDesc: prometheus.NewDesc("keenetic_interface_channel", "Interface wifi channel", labels, nil), } } func (c *InterfaceCollector) Name() string { return "interface" } func (c *InterfaceCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.linkDesc ch <- c.stateDesc ch <- c.mtuDesc ch <- c.txQueueLengthDesc ch <- c.uptimeDesc ch <- c.temperatureDesc ch <- c.channelDesc } func (c *InterfaceCollector) Collect(dev *device.Device, ch chan<- prometheus.Metric) error { var ifaceData any var ok bool hostname := dev.Name client := dev.Client // Check cache first dev.CacheMutex.RLock() ifaceData, ok = dev.Cache["interface"] valid := time.Since(dev.LastUpdate) < dev.CacheTTL dev.CacheMutex.RUnlock() // Fetch fresh data if cache miss or expired if !ok || !valid { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) fresh, err := client.GetInterfaceInfo(ctx) cancel() if err != nil { log.Printf("failed to get interface info from %s: %v", hostname, err) return err } ifaceData = fresh dev.CacheMutex.Lock() dev.Cache["interface"] = fresh dev.LastUpdate = time.Now() dev.CacheMutex.Unlock() } // Type assertion data, ok := ifaceData.(map[string]*keenetic.InterfaceInfo) if !ok || data == nil { log.Printf("invalid cache data type for interface info on %s", hostname) return fmt.Errorf("invalid cache data type for interface info") } // Emit metrics for each interface for _, iface := range data { labels := []string{ hostname, iface.ID, iface.InterfaceName, iface.Type, iface.Description, iface.MAC, iface.Connected, } ch <- prometheus.MustNewConstMetric(c.linkDesc, prometheus.GaugeValue, utils.BoolToFloat(iface.Link == "up"), labels...) ch <- prometheus.MustNewConstMetric(c.stateDesc, prometheus.GaugeValue, utils.BoolToFloat(iface.State == "up"), labels...) ch <- prometheus.MustNewConstMetric(c.mtuDesc, prometheus.GaugeValue, float64(iface.MTU), labels...) ch <- prometheus.MustNewConstMetric(c.txQueueLengthDesc, prometheus.GaugeValue, float64(iface.TxQueueLength), labels...) ch <- prometheus.MustNewConstMetric(c.uptimeDesc, prometheus.CounterValue, float64(iface.Uptime), labels...) ch <- prometheus.MustNewConstMetric(c.temperatureDesc, prometheus.GaugeValue, float64(iface.Temperature), labels...) ch <- prometheus.MustNewConstMetric(c.channelDesc, prometheus.GaugeValue, float64(iface.Channel), labels...) } return nil }