Files
Distributed-Prometheus/edge-agent/apps/onvif-exporter/main.go
jack ab1515dffb refactor: config/apps 目录重组、文档重构、架构图收窄
- 中央:config/(prometheus,alertmanager,grafana)、apps/(tile-cache,topology-editor)
- 边缘:config/(vmagent,blackbox,targets)、apps/(onvif-exporter)
- env: TRAEFIK_PROVIDER、prometheus/env.example 详细说明
- 文档:README/doc 重构,EDGE_CACHE 合并到 EDGE_AGENT_CONFIG
- targets.csv 更新流程说明,ARCHITECTURE 图收窄

Made-with: Cursor
2026-02-28 22:05:43 -05:00

127 lines
3.3 KiB
Go

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
}