package main import ( "encoding/json" "log" "net/http" "os" "strings" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/use-go/onvif" "github.com/use-go/onvif/device" ) const ( defaultTargetsFile = "/config/targets.json" defaultPort = "9600" ) type targetGroup struct { Targets []string `json:"targets"` Labels map[string]string `json:"labels"` } func main() { targetsFile := os.Getenv("TARGETS_FILE") if targetsFile == "" { targetsFile = defaultTargetsFile } port := os.Getenv("EXPORTER_PORT") if port == "" { port = defaultPort } up := prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "onvif_device_up", Help: "1 if ONVIF device is reachable, 0 otherwise", }, []string{"instance", "location", "model", "device_type", "lat", "lon"}) duration := prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "onvif_probe_duration_seconds", Help: "ONVIF probe duration in seconds", }, []string{"instance", "location", "model", "device_type", "lat", "lon"}) reg := prometheus.NewRegistry() reg.MustRegister(up, duration) go func() { ticker := time.NewTicker(60 * time.Second) defer ticker.Stop() for ; true; <-ticker.C { probe(targetsFile, up, duration) } }() // 启动时立即探测一次 probe(targetsFile, up, duration) http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{EnableOpenMetrics: true})) http.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) log.Printf("ONVIF exporter listening on :%s", port) if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatal(err) } } func probe(targetsFile string, up, duration *prometheus.GaugeVec) { data, err := os.ReadFile(targetsFile) if err != nil { log.Printf("read targets file: %v", err) return } var groups []targetGroup if err := json.Unmarshal(data, &groups); err != nil { log.Printf("parse targets: %v", err) return } for _, g := range groups { if len(g.Targets) == 0 { continue } ip := strings.TrimSpace(g.Targets[0]) port := "80" if p, ok := g.Labels["onvif_port"]; ok && p != "" { port = strings.TrimSpace(p) } user := strings.TrimSpace(g.Labels["username"]) pass := strings.TrimSpace(g.Labels["password"]) location := strings.TrimSpace(g.Labels["location"]) lat := strings.TrimSpace(g.Labels["lat"]) lon := strings.TrimSpace(g.Labels["lon"]) model := strings.TrimSpace(g.Labels["model"]) deviceType := strings.TrimSpace(g.Labels["device_type"]) instance := ip + ":" + port labels := prometheus.Labels{ "instance": instance, "location": location, "model": model, "device_type": deviceType, "lat": lat, "lon": lon, } start := time.Now() err := probeONVIF(ip, port, user, pass) elapsed := time.Since(start).Seconds() duration.With(labels).Set(elapsed) if err != nil { log.Printf("onvif probe %s: %v", instance, err) up.With(labels).Set(0) } else { up.With(labels).Set(1) } } } func probeONVIF(ip, port, username, password string) error { xaddr := ip + ":" + port params := onvif.DeviceParams{Xaddr: xaddr, Username: username, Password: password} dev, err := onvif.NewDevice(params) if err != nil { return err } _, err = dev.CallMethod(device.GetDeviceInformation{}) return err }