mirror of
https://github.com/unpoller/unpoller.git
synced 2026-04-05 08:54:09 -04:00
Add a per-controller `<namespace>_controller_up` Prometheus GaugeVec with a `source` label (controller URL or configured ID). The gauge is set to 1 after each successful poll and 0 on failure, giving operators a standard metric to alert on controller connectivity issues. Changes: - pkg/poller/config.go: add ControllerStatus type and ControllerStatuses field to Metrics so any output plugin can consume per-controller health. - pkg/poller/inputs.go: merge ControllerStatuses when AppendMetrics is called (multiple input sources). - pkg/inputunifi/interface.go: populate ControllerStatuses with Up=true on success and Up=false (while still continuing) on per-controller error. - pkg/promunifi/collector.go: declare and register a prometheus.GaugeVec `<namespace>_controller_up`; set the gauge for each controller status after every Collect cycle. Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -256,9 +256,34 @@ func (u *InputUnifi) Metrics(filter *poller.Filter) (*poller.Metrics, error) {
|
|||||||
// Log error but continue to next controller
|
// Log error but continue to next controller
|
||||||
u.LogErrorf("Failed to collect metrics from controller %s: %v", c.URL, err)
|
u.LogErrorf("Failed to collect metrics from controller %s: %v", c.URL, err)
|
||||||
collectionErrors = append(collectionErrors, fmt.Errorf("%s: %w", c.URL, err))
|
collectionErrors = append(collectionErrors, fmt.Errorf("%s: %w", c.URL, err))
|
||||||
|
|
||||||
|
// Record controller as down so output plugins can expose the status.
|
||||||
|
source := c.URL
|
||||||
|
if c.ID != "" {
|
||||||
|
source = c.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics.ControllerStatuses = append(metrics.ControllerStatuses, poller.ControllerStatus{
|
||||||
|
Source: source,
|
||||||
|
Up: false,
|
||||||
|
})
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record controller as up.
|
||||||
|
source := c.URL
|
||||||
|
if c.ID != "" {
|
||||||
|
source = c.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if m != nil {
|
||||||
|
m.ControllerStatuses = append(m.ControllerStatuses, poller.ControllerStatus{
|
||||||
|
Source: source,
|
||||||
|
Up: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
metrics = poller.AppendMetrics(metrics, m)
|
metrics = poller.AppendMetrics(metrics, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,15 @@ type Flags struct {
|
|||||||
*pflag.FlagSet
|
*pflag.FlagSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ControllerStatus carries the per-controller poll result (up/down) so that
|
||||||
|
// output plugins can expose a health gauge without knowing UniFi internals.
|
||||||
|
type ControllerStatus struct {
|
||||||
|
// Source is a stable identifier for the controller (URL or configured ID).
|
||||||
|
Source string
|
||||||
|
// Up is true when the last poll of this controller succeeded.
|
||||||
|
Up bool
|
||||||
|
}
|
||||||
|
|
||||||
// Metrics is a type shared by the exporting and reporting packages.
|
// Metrics is a type shared by the exporting and reporting packages.
|
||||||
type Metrics struct {
|
type Metrics struct {
|
||||||
TS time.Time
|
TS time.Time
|
||||||
@@ -93,6 +102,7 @@ type Metrics struct {
|
|||||||
DHCPLeases []any
|
DHCPLeases []any
|
||||||
WANConfigs []any
|
WANConfigs []any
|
||||||
Sysinfos []any
|
Sysinfos []any
|
||||||
|
ControllerStatuses []ControllerStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events defines the type for log entries.
|
// Events defines the type for log entries.
|
||||||
|
|||||||
@@ -277,6 +277,7 @@ func AppendMetrics(existing *Metrics, m *Metrics) *Metrics {
|
|||||||
existing.DHCPLeases = append(existing.DHCPLeases, m.DHCPLeases...)
|
existing.DHCPLeases = append(existing.DHCPLeases, m.DHCPLeases...)
|
||||||
existing.WANConfigs = append(existing.WANConfigs, m.WANConfigs...)
|
existing.WANConfigs = append(existing.WANConfigs, m.WANConfigs...)
|
||||||
existing.Sysinfos = append(existing.Sysinfos, m.Sysinfos...)
|
existing.Sysinfos = append(existing.Sysinfos, m.Sysinfos...)
|
||||||
|
existing.ControllerStatuses = append(existing.ControllerStatuses, m.ControllerStatuses...)
|
||||||
|
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ type promUnifi struct {
|
|||||||
DHCPLease *dhcplease
|
DHCPLease *dhcplease
|
||||||
WAN *wan
|
WAN *wan
|
||||||
Controller *controller
|
Controller *controller
|
||||||
|
// controllerUp tracks per-controller poll success (1) or failure (0).
|
||||||
|
controllerUp *prometheus.GaugeVec
|
||||||
// This interface is passed to the Collect() method. The Collect method uses
|
// This interface is passed to the Collect() method. The Collect method uses
|
||||||
// this interface to retrieve the latest UniFi measurements and export them.
|
// this interface to retrieve the latest UniFi measurements and export them.
|
||||||
Collector poller.Collect
|
Collector poller.Collect
|
||||||
@@ -213,6 +215,10 @@ func (u *promUnifi) Run(c poller.Collect) error {
|
|||||||
u.DHCPLease = descDHCPLease(u.Namespace + "_")
|
u.DHCPLease = descDHCPLease(u.Namespace + "_")
|
||||||
u.WAN = descWAN(u.Namespace + "_")
|
u.WAN = descWAN(u.Namespace + "_")
|
||||||
u.Controller = descController(u.Namespace + "_")
|
u.Controller = descController(u.Namespace + "_")
|
||||||
|
u.controllerUp = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: u.Namespace + "_controller_up",
|
||||||
|
Help: "Whether the last poll of the UniFi controller succeeded (1) or failed (0).",
|
||||||
|
}, []string{"source"})
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
promver.Version = version.Version
|
promver.Version = version.Version
|
||||||
@@ -221,6 +227,7 @@ func (u *promUnifi) Run(c poller.Collect) error {
|
|||||||
|
|
||||||
webserver.UpdateOutput(&webserver.Output{Name: PluginName, Config: u.Config})
|
webserver.UpdateOutput(&webserver.Output{Name: PluginName, Config: u.Config})
|
||||||
prometheus.MustRegister(collectors.NewBuildInfoCollector())
|
prometheus.MustRegister(collectors.NewBuildInfoCollector())
|
||||||
|
prometheus.MustRegister(u.controllerUp)
|
||||||
prometheus.MustRegister(u)
|
prometheus.MustRegister(u)
|
||||||
mux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer,
|
mux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer,
|
||||||
promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError},
|
promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError},
|
||||||
@@ -340,6 +347,18 @@ func (u *promUnifi) collect(ch chan<- prometheus.Metric, filter *poller.Filter)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export per-controller up/down gauge values.
|
||||||
|
if u.controllerUp != nil && r.Metrics != nil {
|
||||||
|
for _, cs := range r.Metrics.ControllerStatuses {
|
||||||
|
val := 0.0
|
||||||
|
if cs.Up {
|
||||||
|
val = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
u.controllerUp.WithLabelValues(cs.Source).Set(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pass Report interface into our collecting and reporting methods.
|
// Pass Report interface into our collecting and reporting methods.
|
||||||
go u.exportMetrics(r, ch, r.ch)
|
go u.exportMetrics(r, ch, r.ch)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user