diff --git a/internal/metrics/exporter.go b/internal/metrics/exporter.go index 48f0038..8fc951f 100644 --- a/internal/metrics/exporter.go +++ b/internal/metrics/exporter.go @@ -17,6 +17,7 @@ type Sample struct { RuntimeSecond int BatteryCharge float64 LoadPercent float64 + PowerNominalW float64 ThresholdSec int Trigger bool BreachCount int @@ -106,6 +107,8 @@ func (e *Exporter) serveMetrics(w http.ResponseWriter, _ *http.Request) { b.WriteString("# TYPE hecate_ups_battery_charge_percent gauge\n") b.WriteString("# HELP hecate_ups_load_percent UPS output load percentage.\n") b.WriteString("# TYPE hecate_ups_load_percent gauge\n") + b.WriteString("# HELP hecate_ups_power_nominal_watts UPS nominal power rating in watts.\n") + b.WriteString("# TYPE hecate_ups_power_nominal_watts gauge\n") b.WriteString("# HELP hecate_ups_threshold_seconds Red-line threshold for runtime-based shutdown.\n") b.WriteString("# TYPE hecate_ups_threshold_seconds gauge\n") b.WriteString("# HELP hecate_ups_trigger_active Whether this UPS source currently breaches shutdown trigger conditions.\n") @@ -131,6 +134,7 @@ func (e *Exporter) serveMetrics(w http.ResponseWriter, _ *http.Request) { b.WriteString(fmt.Sprintf("hecate_ups_runtime_seconds%s %d\n", labels, s.RuntimeSecond)) b.WriteString(fmt.Sprintf("hecate_ups_battery_charge_percent%s %.2f\n", labels, s.BatteryCharge)) b.WriteString(fmt.Sprintf("hecate_ups_load_percent%s %.2f\n", labels, s.LoadPercent)) + b.WriteString(fmt.Sprintf("hecate_ups_power_nominal_watts%s %.2f\n", labels, s.PowerNominalW)) b.WriteString(fmt.Sprintf("hecate_ups_threshold_seconds%s %d\n", labels, s.ThresholdSec)) b.WriteString(fmt.Sprintf("hecate_ups_trigger_active%s %d\n", labels, boolNum(s.Trigger))) b.WriteString(fmt.Sprintf("hecate_ups_breach_count%s %d\n", labels, s.BreachCount)) diff --git a/internal/metrics/exporter_test.go b/internal/metrics/exporter_test.go index 2ea75f8..f0f3269 100644 --- a/internal/metrics/exporter_test.go +++ b/internal/metrics/exporter_test.go @@ -18,6 +18,7 @@ func TestExporterEmitsCoreMetrics(t *testing.T) { RuntimeSecond: 412, BatteryCharge: 81.5, LoadPercent: 27.4, + PowerNominalW: 510, ThresholdSec: 354, Trigger: true, BreachCount: 2, @@ -38,6 +39,7 @@ func TestExporterEmitsCoreMetrics(t *testing.T) { "hecate_ups_runtime_seconds{source=\"db-ups\",target=\"atlasups@localhost\"", "hecate_ups_battery_charge_percent{source=\"db-ups\",target=\"atlasups@localhost\"", "hecate_ups_load_percent{source=\"db-ups\",target=\"atlasups@localhost\"", + "hecate_ups_power_nominal_watts{source=\"db-ups\",target=\"atlasups@localhost\"", "hecate_ups_threshold_seconds{source=\"db-ups\",target=\"atlasups@localhost\"", } for _, m := range mustContain { diff --git a/internal/service/daemon.go b/internal/service/daemon.go index a14c014..4e648d1 100644 --- a/internal/service/daemon.go +++ b/internal/service/daemon.go @@ -131,6 +131,7 @@ func (d *Daemon) Run(ctx context.Context) error { RuntimeSecond: sample.RuntimeSeconds, BatteryCharge: sample.BatteryCharge, LoadPercent: sample.LoadPercent, + PowerNominalW: sample.NominalPowerW, ThresholdSec: threshold, Trigger: trigger, BreachCount: breachCount[target.Name], diff --git a/internal/ups/nut.go b/internal/ups/nut.go index 87b9ed7..d76357a 100644 --- a/internal/ups/nut.go +++ b/internal/ups/nut.go @@ -16,6 +16,7 @@ type Sample struct { RuntimeSeconds int BatteryCharge float64 LoadPercent float64 + NominalPowerW float64 RawStatus string } @@ -95,6 +96,11 @@ func parseNUT(raw string) (Sample, error) { out.LoadPercent = load } } + if nominalRaw := kv["ups.realpower.nominal"]; nominalRaw != "" { + if nominal, ok := parseNumber(nominalRaw); ok { + out.NominalPowerW = nominal + } + } return out, nil } diff --git a/internal/ups/nut_test.go b/internal/ups/nut_test.go index 58cc4ff..3f83c4f 100644 --- a/internal/ups/nut_test.go +++ b/internal/ups/nut_test.go @@ -3,9 +3,10 @@ package ups import "testing" func TestParseNUT(t *testing.T) { - raw := `battery.runtime: 384 +raw := `battery.runtime: 384 battery.charge: 72 ups.load: 19 +ups.realpower.nominal: 510 ups.status: OB LB ` s, err := parseNUT(raw) @@ -27,4 +28,7 @@ ups.status: OB LB if s.LoadPercent != 19 { t.Fatalf("expected load 19, got %.2f", s.LoadPercent) } + if s.NominalPowerW != 510 { + t.Fatalf("expected nominal power 510, got %.2f", s.NominalPowerW) + } }