mirror of
https://github.com/mdlayher/apcupsd_exporter.git
synced 2024-11-21 10:45:12 +01:00
apcupsd_exporter: bump apcupsd, rework metrics to be more idiomatic
Signed-off-by: Matt Layher <mdlayher@gmail.com>
This commit is contained in:
parent
4fd6f2de88
commit
9c346341fd
2
go.mod
2
go.mod
@ -4,7 +4,7 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.4.0 // indirect
|
||||
github.com/mdlayher/apcupsd v0.0.0-20190314144147-eb3dd99a75fe
|
||||
github.com/mdlayher/apcupsd v0.0.0-20200427150358-149abe0674c2
|
||||
github.com/prometheus/client_golang v1.5.1
|
||||
github.com/prometheus/procfs v0.0.11 // indirect
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect
|
||||
|
4
go.sum
4
go.sum
@ -42,8 +42,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mdlayher/apcupsd v0.0.0-20190314144147-eb3dd99a75fe h1:yMrL+YorbzaBpj/h3BbLMP+qeslPZYMbzcpHFBNy1Yk=
|
||||
github.com/mdlayher/apcupsd v0.0.0-20190314144147-eb3dd99a75fe/go.mod h1:y3mw3VG+t0m20OMqpG8RQqw8cDXvShVb+L8Z8FEnebw=
|
||||
github.com/mdlayher/apcupsd v0.0.0-20200427150358-149abe0674c2 h1:/6kamK5dQLpKSHNH47TqdeDLorFi1rpIeajupArlxxI=
|
||||
github.com/mdlayher/apcupsd v0.0.0-20200427150358-149abe0674c2/go.mod h1:izgPry3rVq8ix9cave/cFB9ZtlrWMa0bUOEOO4Q85x0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
|
265
upscollector.go
265
upscollector.go
@ -18,6 +18,8 @@ type StatusSource interface {
|
||||
|
||||
// A UPSCollector is a Prometheus collector for metrics regarding an APC UPS.
|
||||
type UPSCollector struct {
|
||||
Info *prometheus.Desc
|
||||
|
||||
UPSLoadPercent *prometheus.Desc
|
||||
BatteryChargePercent *prometheus.Desc
|
||||
LineVolts *prometheus.Desc
|
||||
@ -40,11 +42,16 @@ var _ prometheus.Collector = &UPSCollector{}
|
||||
|
||||
// NewUPSCollector creates a new UPSCollector.
|
||||
func NewUPSCollector(ss StatusSource) *UPSCollector {
|
||||
var (
|
||||
labels = []string{"hostname", "ups_name", "model"}
|
||||
)
|
||||
labels := []string{"ups"}
|
||||
|
||||
return &UPSCollector{
|
||||
Info: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "info"),
|
||||
"Metadata about a given UPS.",
|
||||
[]string{"ups", "hostname", "model"},
|
||||
nil,
|
||||
),
|
||||
|
||||
UPSLoadPercent: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "ups_load_percent"),
|
||||
"Current UPS load percentage.",
|
||||
@ -147,141 +154,11 @@ func NewUPSCollector(ss StatusSource) *UPSCollector {
|
||||
}
|
||||
}
|
||||
|
||||
// collect begins a metrics collection task for all metrics related to an APC
|
||||
// UPS.
|
||||
func (c *UPSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) {
|
||||
s, err := c.ss.Status()
|
||||
if err != nil {
|
||||
return c.BatteryVolts, err
|
||||
}
|
||||
|
||||
labels := []string{
|
||||
s.Hostname,
|
||||
s.UPSName,
|
||||
s.Model,
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.UPSLoadPercent,
|
||||
prometheus.GaugeValue,
|
||||
s.LoadPercent,
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryChargePercent,
|
||||
prometheus.GaugeValue,
|
||||
s.BatteryChargePercent,
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LineVolts,
|
||||
prometheus.GaugeValue,
|
||||
s.LineVoltage,
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LineNominalVolts,
|
||||
prometheus.GaugeValue,
|
||||
s.NominalInputVoltage,
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryVolts,
|
||||
prometheus.GaugeValue,
|
||||
s.BatteryVoltage,
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryNominalVolts,
|
||||
prometheus.GaugeValue,
|
||||
s.NominalBatteryVoltage,
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryNumberTransfersTotal,
|
||||
prometheus.CounterValue,
|
||||
float64(s.NumberTransfers),
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryTimeLeftSeconds,
|
||||
prometheus.GaugeValue,
|
||||
s.TimeLeft.Seconds(),
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryTimeOnSeconds,
|
||||
prometheus.GaugeValue,
|
||||
s.TimeOnBattery.Seconds(),
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryCumulativeTimeOnSecondsTotal,
|
||||
prometheus.CounterValue,
|
||||
s.CumulativeTimeOnBattery.Seconds(),
|
||||
labels...,
|
||||
)
|
||||
|
||||
collectTimestamp(
|
||||
ch,
|
||||
c.LastTransferOnBattery,
|
||||
s.XOnBattery,
|
||||
labels...,
|
||||
)
|
||||
|
||||
collectTimestamp(
|
||||
ch,
|
||||
c.LastTransferOffBattery,
|
||||
s.XOffBattery,
|
||||
labels...,
|
||||
)
|
||||
|
||||
collectTimestamp(
|
||||
ch,
|
||||
c.LastSelftest,
|
||||
s.LastSelftest,
|
||||
labels...,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.NominalPowerWatts,
|
||||
prometheus.GaugeValue,
|
||||
float64(s.NominalPower),
|
||||
labels...,
|
||||
)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// collectTimestamp collects timestamp metrics.
|
||||
// Timestamps that are zero (time.IsZero() == true) are ignored, as such a timestamp indicates
|
||||
// 'information not available', which is best expressed in Prometheus by not having the metric at all.
|
||||
func collectTimestamp(ch chan<- prometheus.Metric, desc *prometheus.Desc, time time.Time, labelValues ...string) {
|
||||
if time.IsZero() {
|
||||
return
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(time.Unix()),
|
||||
labelValues...,
|
||||
)
|
||||
}
|
||||
|
||||
// Describe sends the descriptors of each metric over to the provided channel.
|
||||
// The corresponding metric values are sent separately.
|
||||
func (c *UPSCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
ds := []*prometheus.Desc{
|
||||
c.Info,
|
||||
c.UPSLoadPercent,
|
||||
c.BatteryChargePercent,
|
||||
c.LineVolts,
|
||||
@ -302,9 +179,123 @@ func (c *UPSCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
// Collect sends the metric values for each metric created by the UPSCollector
|
||||
// to the provided prometheus Metric channel.
|
||||
func (c *UPSCollector) Collect(ch chan<- prometheus.Metric) {
|
||||
if desc, err := c.collect(ch); err != nil {
|
||||
log.Printf("[ERROR] failed collecting UPS metric %v: %v", desc, err)
|
||||
ch <- prometheus.NewInvalidMetric(desc, err)
|
||||
s, err := c.ss.Status()
|
||||
if err != nil {
|
||||
log.Printf("failed collecting UPS metrics: %v", err)
|
||||
ch <- prometheus.NewInvalidMetric(c.Info, err)
|
||||
return
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.Info,
|
||||
prometheus.GaugeValue,
|
||||
1,
|
||||
s.UPSName, s.Hostname, s.Model,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.UPSLoadPercent,
|
||||
prometheus.GaugeValue,
|
||||
s.LoadPercent,
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryChargePercent,
|
||||
prometheus.GaugeValue,
|
||||
s.BatteryChargePercent,
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LineVolts,
|
||||
prometheus.GaugeValue,
|
||||
s.LineVoltage,
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LineNominalVolts,
|
||||
prometheus.GaugeValue,
|
||||
s.NominalInputVoltage,
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryVolts,
|
||||
prometheus.GaugeValue,
|
||||
s.BatteryVoltage,
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryNominalVolts,
|
||||
prometheus.GaugeValue,
|
||||
s.NominalBatteryVoltage,
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryNumberTransfersTotal,
|
||||
prometheus.CounterValue,
|
||||
float64(s.NumberTransfers),
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryTimeLeftSeconds,
|
||||
prometheus.GaugeValue,
|
||||
s.TimeLeft.Seconds(),
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryTimeOnSeconds,
|
||||
prometheus.GaugeValue,
|
||||
s.TimeOnBattery.Seconds(),
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BatteryCumulativeTimeOnSecondsTotal,
|
||||
prometheus.CounterValue,
|
||||
s.CumulativeTimeOnBattery.Seconds(),
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LastTransferOnBattery,
|
||||
prometheus.GaugeValue,
|
||||
timestamp(s.XOnBattery),
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LastTransferOffBattery,
|
||||
prometheus.GaugeValue,
|
||||
timestamp(s.XOffBattery),
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LastSelftest,
|
||||
prometheus.GaugeValue,
|
||||
timestamp(s.LastSelftest),
|
||||
s.UPSName,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.NominalPowerWatts,
|
||||
prometheus.GaugeValue,
|
||||
float64(s.NominalPower),
|
||||
s.UPSName,
|
||||
)
|
||||
}
|
||||
|
||||
func timestamp(t time.Time) float64 {
|
||||
if t.IsZero() {
|
||||
return 0
|
||||
}
|
||||
|
||||
return float64(t.Unix())
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package apcupsdexporter
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -20,17 +19,14 @@ func TestUPSCollector(t *testing.T) {
|
||||
ss: &testStatusSource{
|
||||
s: &apcupsd.Status{},
|
||||
},
|
||||
matches: []*regexp.Regexp{
|
||||
regexp.MustCompile(`apcupsd_battery_charge_percent{hostname="",model="",ups_name=""} 0`),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "full",
|
||||
ss: &testStatusSource{
|
||||
s: &apcupsd.Status{
|
||||
Hostname: "a",
|
||||
Model: "b",
|
||||
UPSName: "c",
|
||||
Hostname: "foo",
|
||||
Model: "APC UPS",
|
||||
UPSName: "bar",
|
||||
|
||||
BatteryChargePercent: 100.0,
|
||||
CumulativeTimeOnBattery: 30 * time.Second,
|
||||
@ -49,20 +45,21 @@ func TestUPSCollector(t *testing.T) {
|
||||
},
|
||||
},
|
||||
matches: []*regexp.Regexp{
|
||||
regexp.MustCompile(`apcupsd_battery_charge_percent{hostname="a",model="b",ups_name="c"} 100`),
|
||||
regexp.MustCompile(`apcupsd_battery_cumulative_time_on_seconds_total{hostname="a",model="b",ups_name="c"} 30`),
|
||||
regexp.MustCompile(`apcupsd_battery_nominal_volts{hostname="a",model="b",ups_name="c"} 12`),
|
||||
regexp.MustCompile(`apcupsd_battery_time_left_seconds{hostname="a",model="b",ups_name="c"} 120`),
|
||||
regexp.MustCompile(`apcupsd_battery_time_on_seconds{hostname="a",model="b",ups_name="c"} 10`),
|
||||
regexp.MustCompile(`apcupsd_battery_volts{hostname="a",model="b",ups_name="c"} 13.2`),
|
||||
regexp.MustCompile(`apcupsd_battery_number_transfers_total{hostname="a",model="b",ups_name="c"} 1`),
|
||||
regexp.MustCompile(`apcupsd_line_nominal_volts{hostname="a",model="b",ups_name="c"} 120`),
|
||||
regexp.MustCompile(`apcupsd_line_volts{hostname="a",model="b",ups_name="c"} 121.1`),
|
||||
regexp.MustCompile(`apcupsd_ups_load_percent{hostname="a",model="b",ups_name="c"} 16`),
|
||||
regexp.MustCompile(`apcupsd_last_transfer_on_battery{hostname="a",model="b",ups_name="c"} 100001`),
|
||||
regexp.MustCompile(`apcupsd_last_transfer_off_battery{hostname="a",model="b",ups_name="c"} 100002`),
|
||||
regexp.MustCompile(`apcupsd_last_selftest{hostname="a",model="b",ups_name="c"} 100003`),
|
||||
regexp.MustCompile(`apcupsd_nominal_power_watts{hostname="a",model="b",ups_name="c"} 50`),
|
||||
regexp.MustCompile(`apcupsd_battery_charge_percent{ups="bar"} 100`),
|
||||
regexp.MustCompile(`apcupsd_battery_cumulative_time_on_seconds_total{ups="bar"} 30`),
|
||||
regexp.MustCompile(`apcupsd_battery_nominal_volts{ups="bar"} 12`),
|
||||
regexp.MustCompile(`apcupsd_battery_time_left_seconds{ups="bar"} 120`),
|
||||
regexp.MustCompile(`apcupsd_battery_time_on_seconds{ups="bar"} 10`),
|
||||
regexp.MustCompile(`apcupsd_battery_volts{ups="bar"} 13.2`),
|
||||
regexp.MustCompile(`apcupsd_battery_number_transfers_total{ups="bar"} 1`),
|
||||
regexp.MustCompile(`apcupsd_info{hostname="foo",model="APC UPS",ups="bar"} 1`),
|
||||
regexp.MustCompile(`apcupsd_line_nominal_volts{ups="bar"} 120`),
|
||||
regexp.MustCompile(`apcupsd_line_volts{ups="bar"} 121.1`),
|
||||
regexp.MustCompile(`apcupsd_ups_load_percent{ups="bar"} 16`),
|
||||
regexp.MustCompile(`apcupsd_last_transfer_on_battery{ups="bar"} 100001`),
|
||||
regexp.MustCompile(`apcupsd_last_transfer_off_battery{ups="bar"} 100002`),
|
||||
regexp.MustCompile(`apcupsd_last_selftest{ups="bar"} 100003`),
|
||||
regexp.MustCompile(`apcupsd_nominal_power_watts{ups="bar"} 50`),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -72,56 +69,14 @@ func TestUPSCollector(t *testing.T) {
|
||||
out := testCollector(t, NewUPSCollector(tt.ss))
|
||||
|
||||
for _, m := range tt.matches {
|
||||
name := metricName(t, m.String())
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if !m.Match(out) {
|
||||
t.Fatalf("\toutput failed to match regex (regexp: %v)", m)
|
||||
}
|
||||
})
|
||||
if !m.Match(out) {
|
||||
t.Fatalf("output failed to match regex (regexp: %v)", m)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestZeroTimesAreIgnored tests that times with a zero value (time.IsZero() == true)
|
||||
// are not collected.
|
||||
func TestZeroTimesAreIgnored(t *testing.T) {
|
||||
ss := &testStatusSource{
|
||||
s: &apcupsd.Status{
|
||||
XOnBattery: time.Unix(123456, 0),
|
||||
XOffBattery: time.Time{},
|
||||
},
|
||||
}
|
||||
out := testCollector(t, NewUPSCollector(ss))
|
||||
// Test that in general timestamps are collected.
|
||||
if !regexp.MustCompile(`apcupsd_last_transfer_on_battery.* 123456`).Match(out) {
|
||||
t.Error("non-zero timestamp is not reported properly")
|
||||
}
|
||||
// Test that zero timestamps, however, are ignored.
|
||||
if regexp.MustCompile(`apcupsd_last_transfer_off_battery`).Match(out) {
|
||||
t.Error("Zero time is reported")
|
||||
}
|
||||
}
|
||||
|
||||
func metricName(t *testing.T, metric string) string {
|
||||
ss := strings.Split(metric, " ")
|
||||
if len(ss) != 2 {
|
||||
t.Fatalf("malformed metric: %v", metric)
|
||||
}
|
||||
|
||||
if !strings.Contains(ss[0], "{") {
|
||||
return ss[0]
|
||||
}
|
||||
|
||||
ss = strings.Split(ss[0], "{")
|
||||
if len(ss) != 2 {
|
||||
t.Fatalf("malformed metric: %v", metric)
|
||||
}
|
||||
|
||||
return ss[0]
|
||||
}
|
||||
|
||||
var _ StatusSource = &testStatusSource{}
|
||||
|
||||
type testStatusSource struct {
|
||||
|
Loading…
Reference in New Issue
Block a user