Add infrastructure reliability monitoring

This commit is contained in:
Maxim
2026-08-01 16:58:25 +03:00
parent f6ac7eeb1a
commit a570537e64
12 changed files with 926 additions and 51 deletions

57
ups.go
View File

@@ -10,20 +10,26 @@ import (
)
type UPSMetrics struct {
Available bool `json:"available"`
Source string `json:"source"`
Name string `json:"name"`
Model string `json:"model"`
Status string `json:"status"`
StatusLabel string `json:"statusLabel"`
ChargePercent *float64 `json:"chargePercent"`
RuntimeSeconds *float64 `json:"runtimeSeconds"`
LoadPercent *float64 `json:"loadPercent"`
InputVoltage *float64 `json:"inputVoltage"`
OutputVoltage *float64 `json:"outputVoltage"`
InputFrequency *float64 `json:"inputFrequency"`
BatteryVoltage *float64 `json:"batteryVoltage"`
Temperature *float64 `json:"temperatureCelsius"`
Available bool `json:"available"`
Source string `json:"source"`
Name string `json:"name"`
Model string `json:"model"`
Status string `json:"status"`
StatusLabel string `json:"statusLabel"`
ChargePercent *float64 `json:"chargePercent"`
RuntimeSeconds *float64 `json:"runtimeSeconds"`
LoadPercent *float64 `json:"loadPercent"`
InputVoltage *float64 `json:"inputVoltage"`
OutputVoltage *float64 `json:"outputVoltage"`
InputFrequency *float64 `json:"inputFrequency"`
BatteryVoltage *float64 `json:"batteryVoltage"`
Temperature *float64 `json:"temperatureCelsius"`
CommunicationOK bool `json:"communicationOk"`
BatteryDate string `json:"batteryDate"`
BatteryAgeDays int `json:"batteryAgeDays,omitempty"`
ReplaceBattery bool `json:"replaceBattery"`
LowBatteryCharge *float64 `json:"lowBatteryCharge,omitempty"`
LowBatteryRuntime *float64 `json:"lowBatteryRuntime,omitempty"`
}
type upsCollector struct {
@@ -43,7 +49,13 @@ func (c *upsCollector) collect() UPSMetrics {
} else if metrics, ok := readAPCUPSD(); ok {
c.cached = metrics
} else {
c.cached = UPSMetrics{}
if c.cached.Available || c.cached.Source != "" {
c.cached.Available = false
c.cached.CommunicationOK = false
c.cached.StatusLabel = "Нет связи"
} else {
c.cached = UPSMetrics{}
}
}
c.updatedAt = time.Now()
return c.cached
@@ -85,6 +97,7 @@ func readAPCUPSD() (UPSMetrics, bool) {
"ups.load": raw["LOADPCT"], "input.voltage": raw["LINEV"],
"output.voltage": raw["OUTPUTV"], "input.frequency": raw["LINEFREQ"],
"battery.voltage": raw["BATTV"], "ups.temperature": raw["ITEMP"],
"battery.date": raw["BATTDATE"], "battery.charge.low": raw["MBATTCHG"], "battery.runtime.low": minutesToSeconds(raw["MINTIMEL"]),
}
return upsFromValues("apcupsd", raw["UPSNAME"], values), true
}
@@ -102,14 +115,24 @@ func parseColonValues(output string) map[string]string {
func upsFromValues(source, name string, values map[string]string) UPSMetrics {
status := values["ups.status"]
return UPSMetrics{
Available: true, Source: source, Name: name, Model: values["device.model"],
metrics := UPSMetrics{
Available: true, CommunicationOK: true, Source: source, Name: name, Model: values["device.model"],
Status: status, StatusLabel: upsStatusLabel(status),
ChargePercent: numberPointer(values["battery.charge"]), RuntimeSeconds: numberPointer(values["battery.runtime"]),
LoadPercent: numberPointer(values["ups.load"]), InputVoltage: numberPointer(values["input.voltage"]),
OutputVoltage: numberPointer(values["output.voltage"]), InputFrequency: numberPointer(values["input.frequency"]),
BatteryVoltage: numberPointer(values["battery.voltage"]), Temperature: numberPointer(firstNonEmpty(values["ups.temperature"], values["battery.temperature"])),
BatteryDate: firstNonEmpty(values["battery.date"], values["battery.mfr.date"]), ReplaceBattery: strings.Contains(status, "RB"), LowBatteryCharge: numberPointer(values["battery.charge.low"]), LowBatteryRuntime: numberPointer(values["battery.runtime.low"]),
}
if metrics.BatteryDate != "" {
for _, layout := range []string{"2006/01/02", "2006-01-02", "01/02/2006", "02/01/2006"} {
if date, err := time.Parse(layout, metrics.BatteryDate); err == nil {
metrics.BatteryAgeDays = int(time.Since(date).Hours() / 24)
break
}
}
}
return metrics
}
func numberPointer(value string) *float64 {