@cryptotaxi247 / netdata-1 / commits / 6c33a0d23

add go.d/apcupsd (#18489)

Ilya Mashchenko committed Sep 8, 2024 at 20:14 UTC 6c33a0d2367191cfcff4fc4fdc550322b607e8c2
18 files changed +1412 -25
src/go/plugin/go.d/README.md
+1
@@ -53,6 +53,7 @@ see the appropriate collector readme.
53 | [activemq](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/activemq) | ActiveMQ |
54 | [ap](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/ap) | Wireless AP |
55 | [apache](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/apache) | Apache |
56 +| [apcupsd](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/apcupsd) | UPS (APC) |
57 | [beanstalk](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/beanstalk) | Beanstalk |
58 | [bind](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/bind) | ISC Bind |
59 | [boinc](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/boinc) | BOINC |
src/go/plugin/go.d/config/go.d.conf
+1
@@ -19,6 +19,7 @@ modules:
19 # activemq: yes
20 # ap: yes
21 # apache: yes
22 +# apcupsd: yes
23 # beanstalk: yes
24 # bind: yes
25 # boinc: yes
src/go/plugin/go.d/config/go.d/apcupsd.conf new
+6
@@ -0,0 +1,6 @@
1 +## All available configuration options, their descriptions and default values:
2 +## https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/apcupsd#readme
3 +
4 +#jobs:
5 +# - name: local
6 +# address: 127.0.0.1:3551
src/go/plugin/go.d/config/go.d/sd/net_listeners.conf
+7
@@ -16,6 +16,8 @@ classify:
16 expr: '{{ and (eq .Port "8161") (eq .Comm "activemq") }}'
17 - tags: "apache"
18 expr: '{{ and (eq .Port "80" "8080") (eq .Comm "apache" "apache2" "httpd") }}'
19 + - tags: "apcupsd"
20 + expr: '{{ or (eq .Port "3551") (eq .Comm "apcupsd") }}'
21 - tags: "beanstalk"
22 expr: '{{ or (eq .Port "11300") (eq .Comm "beanstalkd") }}'
23 - tags: "boinc"
@@ -159,6 +161,11 @@ compose:
161 module: apache
162 name: local
163 url: http://{{.Address}}/server-status?auto
164 + - selector: "apcupsd"
165 + template: |
166 + module: apcupsd
167 + name: local_{{.Port}}
168 + address: {{.Address}}
169 - selector: "beanstalk"
170 template: |
171 module: beanstalk
src/go/plugin/go.d/modules/apcupsd/apcupsd.go new
+100
@@ -0,0 +1,100 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package apcupsd
4 +
5 +import (
6 + _ "embed"
7 + "errors"
8 + "time"
9 +
10 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
11 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
12 +)
13 +
14 +//go:embed "config_schema.json"
15 +var configSchema string
16 +
17 +func init() {
18 + module.Register("apcupsd", module.Creator{
19 + JobConfigSchema: configSchema,
20 + Create: func() module.Module { return New() },
21 + Config: func() any { return &Config{} },
22 + })
23 +}
24 +
25 +func New() *Apcupsd {
26 + return &Apcupsd{
27 + Config: Config{
28 + Address: "127.0.0.1:3551",
29 + Timeout: web.Duration(time.Second * 3),
30 + },
31 + newConn: newUpsdConn,
32 + charts: charts.Copy(),
33 + }
34 +}
35 +
36 +type Config struct {
37 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
38 + Address string `yaml:"address" json:"address"`
39 + Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"`
40 +}
41 +
42 +type Apcupsd struct {
43 + module.Base
44 + Config `yaml:",inline" json:""`
45 +
46 + charts *module.Charts
47 +
48 + conn apcupsdConn
49 + newConn func(Config) apcupsdConn
50 +}
51 +
52 +func (a *Apcupsd) Configuration() any {
53 + return a.Config
54 +}
55 +
56 +func (a *Apcupsd) Init() error {
57 + if a.Address == "" {
58 + a.Error("config: 'address' not set")
59 + return errors.New("address not set")
60 + }
61 +
62 + return nil
63 +}
64 +
65 +func (a *Apcupsd) Check() error {
66 + mx, err := a.collect()
67 + if err != nil {
68 + a.Error(err)
69 + return err
70 + }
71 + if len(mx) == 0 {
72 + return errors.New("no metrics collected")
73 + }
74 + return nil
75 +}
76 +
77 +func (a *Apcupsd) Charts() *module.Charts {
78 + return a.charts
79 +}
80 +
81 +func (a *Apcupsd) Collect() map[string]int64 {
82 + mx, err := a.collect()
83 + if err != nil {
84 + a.Error(err)
85 + }
86 +
87 + if len(mx) == 0 {
88 + return nil
89 + }
90 + return mx
91 +}
92 +
93 +func (a *Apcupsd) Cleanup() {
94 + if a.conn != nil {
95 + if err := a.conn.disconnect(); err != nil {
96 + a.Warningf("error on disconnect: %v", err)
97 + }
98 + a.conn = nil
99 + }
100 +}
src/go/plugin/go.d/modules/apcupsd/apcupsd_test.go new
+283
@@ -0,0 +1,283 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package apcupsd
4 +
5 +import (
6 + "errors"
7 + "os"
8 + "strings"
9 + "testing"
10 +
11 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
12 +
13 + "github.com/stretchr/testify/assert"
14 + "github.com/stretchr/testify/require"
15 +)
16 +
17 +var (
18 + dataConfigJSON, _ = os.ReadFile("testdata/config.json")
19 + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
20 +
21 + dataStatus, _ = os.ReadFile("testdata/status.txt")
22 + dataStatusCommlost, _ = os.ReadFile("testdata/status_commlost.txt")
23 +)
24 +
25 +func Test_testDataIsValid(t *testing.T) {
26 + for name, data := range map[string][]byte{
27 + "dataConfigJSON": dataConfigJSON,
28 + "dataConfigYAML": dataConfigYAML,
29 + "dataStatus": dataStatus,
30 + "dataStatusCommlost": dataStatusCommlost,
31 + } {
32 + require.NotNil(t, data, name)
33 + }
34 +}
35 +
36 +func TestApcupsd_ConfigurationSerialize(t *testing.T) {
37 + module.TestConfigurationSerialize(t, &Apcupsd{}, dataConfigJSON, dataConfigYAML)
38 +}
39 +
40 +func TestApcupsd_Cleanup(t *testing.T) {
41 + apc := New()
42 +
43 + require.NotPanics(t, apc.Cleanup)
44 +
45 + mock := prepareMockOk()
46 + apc.newConn = func(Config) apcupsdConn { return mock }
47 +
48 + require.NoError(t, apc.Init())
49 + _ = apc.Collect()
50 + require.NotPanics(t, apc.Cleanup)
51 + assert.True(t, mock.calledDisconnect)
52 +}
53 +
54 +func TestApcupsd_Init(t *testing.T) {
55 + tests := map[string]struct {
56 + config Config
57 + wantFail bool
58 + }{
59 + "success on default config": {
60 + wantFail: false,
61 + config: New().Config,
62 + },
63 + "fails when 'address' option not set": {
64 + wantFail: true,
65 + config: Config{Address: ""},
66 + },
67 + }
68 +
69 + for name, test := range tests {
70 + t.Run(name, func(t *testing.T) {
71 + apc := New()
72 + apc.Config = test.config
73 +
74 + if test.wantFail {
75 + assert.Error(t, apc.Init())
76 + } else {
77 + assert.NoError(t, apc.Init())
78 + }
79 + })
80 + }
81 +}
82 +
83 +func TestApcupsd_Check(t *testing.T) {
84 + tests := map[string]struct {
85 + prepareMock func() *mockApcupsdConn
86 + wantFail bool
87 + }{
88 + "case ok": {
89 + wantFail: false,
90 + prepareMock: prepareMockOk,
91 + },
92 + "case commlost": {
93 + wantFail: false,
94 + prepareMock: prepareMockOkCommlost,
95 + },
96 + "error on connect()": {
97 + wantFail: true,
98 + prepareMock: prepareMockErrOnConnect,
99 + },
100 + "error on status()": {
101 + wantFail: true,
102 + prepareMock: prepareMockErrOnStatus,
103 + },
104 + }
105 +
106 + for name, test := range tests {
107 + t.Run(name, func(t *testing.T) {
108 + apc := New()
109 + apc.newConn = func(Config) apcupsdConn { return test.prepareMock() }
110 +
111 + require.NoError(t, apc.Init())
112 +
113 + if test.wantFail {
114 + assert.Error(t, apc.Check())
115 + } else {
116 + assert.NoError(t, apc.Check())
117 + }
118 + })
119 + }
120 +}
121 +
122 +func TestApcupsd_Charts(t *testing.T) {
123 + apc := New()
124 + require.NoError(t, apc.Init())
125 + assert.NotNil(t, apc.Charts())
126 +}
127 +
128 +func TestApcupsd_Collect(t *testing.T) {
129 + tests := map[string]struct {
130 + prepareMock func() *mockApcupsdConn
131 + wantCollected map[string]int64
132 + wantCharts int
133 + wantConnDisconnect bool
134 + }{
135 + "case ok": {
136 + prepareMock: prepareMockOk,
137 + wantCollected: map[string]int64{
138 + "battery_charge": 10000,
139 + "battery_seconds_since_replacement": 86400,
140 + "battery_voltage": 2790,
141 + "battery_voltage_nominal": 2400,
142 + "input_frequency": 5000,
143 + "input_voltage": 23530,
144 + "input_voltage_max": 23920,
145 + "input_voltage_min": 23400,
146 + "itemp": 3279,
147 + "load": 55,
148 + "load_percent": 930,
149 + "output_voltage": 23660,
150 + "output_voltage_nominal": 23000,
151 + "selftest_BT": 0,
152 + "selftest_IP": 0,
153 + "selftest_NG": 0,
154 + "selftest_NO": 1,
155 + "selftest_OK": 0,
156 + "selftest_UNK": 0,
157 + "selftest_WN": 0,
158 + "status_BOOST": 0,
159 + "status_CAL": 0,
160 + "status_COMMLOST": 0,
161 + "status_LOWBATT": 0,
162 + "status_NOBATT": 0,
163 + "status_ONBATT": 0,
164 + "status_ONLINE": 1,
165 + "status_OVERLOAD": 0,
166 + "status_REPLACEBATT": 0,
167 + "status_SHUTTING_DOWN": 0,
168 + "status_SLAVE": 0,
169 + "status_SLAVEDOWN": 0,
170 + "status_TRIM": 0,
171 + "timeleft": 780000,
172 + },
173 + wantConnDisconnect: false,
174 + },
175 + "case commlost": {
176 + prepareMock: prepareMockOkCommlost,
177 + wantCollected: map[string]int64{
178 + "status_BOOST": 0,
179 + "status_CAL": 0,
180 + "status_COMMLOST": 1,
181 + "status_LOWBATT": 0,
182 + "status_NOBATT": 0,
183 + "status_ONBATT": 0,
184 + "status_ONLINE": 0,
185 + "status_OVERLOAD": 0,
186 + "status_REPLACEBATT": 0,
187 + "status_SHUTTING_DOWN": 0,
188 + "status_SLAVE": 0,
189 + "status_SLAVEDOWN": 0,
190 + "status_TRIM": 0,
191 + },
192 + wantConnDisconnect: false,
193 + },
194 + "error on connect()": {
195 + prepareMock: prepareMockErrOnConnect,
196 + wantCollected: nil,
197 + wantConnDisconnect: false,
198 + },
199 + "error on status()": {
200 + prepareMock: prepareMockErrOnStatus,
201 + wantCollected: nil,
202 + wantConnDisconnect: true,
203 + },
204 + }
205 +
206 + for name, test := range tests {
207 + t.Run(name, func(t *testing.T) {
208 + apc := New()
209 + require.NoError(t, apc.Init())
210 +
211 + mock := test.prepareMock()
212 + apc.newConn = func(Config) apcupsdConn { return mock }
213 +
214 + mx := apc.Collect()
215 +
216 + if _, ok := mx["battery_seconds_since_replacement"]; ok {
217 + mx["battery_seconds_since_replacement"] = 86400
218 + }
219 +
220 + assert.Equal(t, test.wantCollected, mx)
221 +
222 + if len(test.wantCollected) > 0 {
223 + if strings.Contains(name, "commlost") {
224 + module.TestMetricsHasAllChartsDimsSkip(t, apc.Charts(), mx, func(chart *module.Chart) bool {
225 + return chart.ID != statusChart.ID
226 + })
227 + } else {
228 + module.TestMetricsHasAllChartsDims(t, apc.Charts(), mx)
229 + }
230 + }
231 +
232 + assert.Equalf(t, test.wantConnDisconnect, mock.calledDisconnect, "calledDisconnect")
233 + })
234 + }
235 +}
236 +
237 +func prepareMockOk() *mockApcupsdConn {
238 + return &mockApcupsdConn{
239 + dataStatus: dataStatus,
240 + }
241 +}
242 +
243 +func prepareMockOkCommlost() *mockApcupsdConn {
244 + return &mockApcupsdConn{
245 + dataStatus: dataStatusCommlost,
246 + }
247 +}
248 +
249 +func prepareMockErrOnConnect() *mockApcupsdConn {
250 + return &mockApcupsdConn{errOnConnect: true}
251 +}
252 +
253 +func prepareMockErrOnStatus() *mockApcupsdConn {
254 + return &mockApcupsdConn{errOnStatus: true}
255 +}
256 +
257 +type mockApcupsdConn struct {
258 + errOnConnect bool
259 + errOnStatus bool
260 + calledDisconnect bool
261 +
262 + dataStatus []byte
263 +}
264 +
265 +func (m *mockApcupsdConn) connect() error {
266 + if m.errOnConnect {
267 + return errors.New("mock error on connect()")
268 + }
269 + return nil
270 +}
271 +
272 +func (m *mockApcupsdConn) disconnect() error {
273 + m.calledDisconnect = true
274 + return nil
275 +}
276 +
277 +func (m *mockApcupsdConn) status() ([]byte, error) {
278 + if m.errOnStatus {
279 + return nil, errors.New("mock error on status()")
280 + }
281 +
282 + return m.dataStatus, nil
283 +}
src/go/plugin/go.d/modules/apcupsd/charts.go new
+224
@@ -0,0 +1,224 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package apcupsd
4 +
5 +import (
6 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
7 +)
8 +
9 +const (
10 + prioUpsStatus = module.Priority + iota
11 + prioUpsSelftest
12 +
13 + prioUpsBatteryCharge
14 + prioUpsBatteryTimeRemaining
15 + prioUpsBatteryTimeSinceReplacement
16 + prioUpsBatteryVoltage
17 +
18 + prioUpsLoadCapacityUtilization
19 + prioUpsLoad
20 +
21 + prioUpsTemperature
22 +
23 + prioUpsInputVoltage
24 + prioUpsInputFrequency
25 +
26 + prioUpsOutputVoltage
27 +)
28 +
29 +var charts = module.Charts{
30 + statusChart.Copy(),
31 + selftestChart.Copy(),
32 +
33 + batteryChargeChart.Copy(),
34 + batteryTimeRemainingChart.Copy(),
35 + batteryTimeSinceReplacementChart.Copy(),
36 + batteryVoltageChart.Copy(),
37 +
38 + loadCapacityUtilizationChart.Copy(),
39 + loadChart.Copy(),
40 +
41 + internalTemperatureChart.Copy(),
42 +
43 + inputVoltageChart.Copy(),
44 + inputFrequencyChart.Copy(),
45 +
46 + outputVoltageChart.Copy(),
47 +}
48 +
49 +// Status
50 +var (
51 + statusChart = func() module.Chart {
52 + chart := module.Chart{
53 + ID: "ups_status",
54 + Title: "UPS Status",
55 + Units: "status",
56 + Fam: "status",
57 + Ctx: "apcupsd.ups_status",
58 + Priority: prioUpsStatus,
59 + Type: module.Line,
60 + }
61 + for _, v := range upsStatuses {
62 + chart.Dims = append(chart.Dims, &module.Dim{ID: "status_" + v, Name: v})
63 + }
64 + return chart
65 + }()
66 + selftestChart = func() module.Chart {
67 + chart := module.Chart{
68 + ID: "ups_selftest",
69 + Title: "UPS Self-Test Status",
70 + Units: "status",
71 + Fam: "status",
72 + Ctx: "apcupsd.ups_selftest",
73 + Priority: prioUpsSelftest,
74 + Type: module.Line,
75 + }
76 + for _, v := range upsSelftestStatuses {
77 + chart.Dims = append(chart.Dims, &module.Dim{ID: "selftest_" + v, Name: v})
78 + }
79 + return chart
80 + }()
81 +)
82 +
83 +// Battery
84 +var (
85 + batteryChargeChart = module.Chart{
86 + ID: "ups_battery_charge",
87 + Title: "UPS Battery Charge",
88 + Units: "percent",
89 + Fam: "battery",
90 + Ctx: "apcupsd.ups_battery_charge",
91 + Priority: prioUpsBatteryCharge,
92 + Type: module.Area,
93 + Dims: module.Dims{
94 + {ID: "battery_charge", Name: "charge", Div: precision},
95 + },
96 + }
97 + batteryTimeRemainingChart = module.Chart{
98 + ID: "ups_battery_time_remaining",
99 + Title: "UPS Estimated Runtime on Battery",
100 + Units: "seconds",
101 + Fam: "battery",
102 + Ctx: "apcupsd.ups_battery_time_remaining",
103 + Priority: prioUpsBatteryTimeRemaining,
104 + Type: module.Line,
105 + Dims: module.Dims{
106 + {ID: "timeleft", Name: "timeleft", Div: precision},
107 + },
108 + }
109 + batteryTimeSinceReplacementChart = module.Chart{
110 + ID: "ups_battery_time_since_replacement",
111 + Title: "UPS Time Since Battery Replacement",
112 + Units: "seconds",
113 + Fam: "battery",
114 + Ctx: "apcupsd.ups_battery_time_since_replacement",
115 + Priority: prioUpsBatteryTimeSinceReplacement,
116 + Type: module.Line,
117 + Dims: module.Dims{
118 + {ID: "battery_seconds_since_replacement", Name: "since_replacement"},
119 + },
120 + }
121 + batteryVoltageChart = module.Chart{
122 + ID: "ups_battery_voltage",
123 + Title: "UPS Battery Voltage",
124 + Units: "Volts",
125 + Fam: "battery",
126 + Ctx: "apcupsd.ups_battery_voltage",
127 + Priority: prioUpsBatteryVoltage,
128 + Type: module.Line,
129 + Dims: module.Dims{
130 + {ID: "battery_voltage", Name: "voltage", Div: precision},
131 + {ID: "battery_voltage_nominal", Name: "nominal_voltage", Div: precision},
132 + },
133 + }
134 +)
135 +
136 +// Load
137 +var (
138 + loadCapacityUtilizationChart = module.Chart{
139 + ID: "ups_load_capacity_utilization",
140 + Title: "UPS Load Capacity Utilization",
141 + Units: "percent",
142 + Fam: "load",
143 + Ctx: "apcupsd.ups_load_capacity_utilization",
144 + Priority: prioUpsLoadCapacityUtilization,
145 + Type: module.Line,
146 + Dims: module.Dims{
147 + {ID: "load_percent", Name: "load", Div: precision},
148 + },
149 + }
150 + loadChart = module.Chart{
151 + ID: "ups_load",
152 + Title: "UPS Load",
153 + Units: "Watts",
154 + Fam: "load",
155 + Ctx: "apcupsd.ups_load",
156 + Priority: prioUpsLoad,
157 + Type: module.Line,
158 + Dims: module.Dims{
159 + {ID: "load", Name: "load", Div: precision},
160 + },
161 + }
162 +)
163 +
164 +// Temperature
165 +var (
166 + internalTemperatureChart = module.Chart{
167 + ID: "ups_temperature",
168 + Title: "UPS Internal Temperature",
169 + Units: "Celsius",
170 + Fam: "temperature",
171 + Ctx: "apcupsd.ups_temperature",
172 + Priority: prioUpsTemperature,
173 + Type: module.Line,
174 + Dims: module.Dims{
175 + {ID: "itemp", Name: "temperature", Div: precision},
176 + },
177 + }
178 +)
179 +
180 +// Input
181 +var (
182 + inputVoltageChart = module.Chart{
183 + ID: "ups_input_voltage",
184 + Title: "UPS Input Voltage",
185 + Units: "Volts",
186 + Fam: "input",
187 + Ctx: "apcupsd.ups_input_voltage",
188 + Priority: prioUpsInputVoltage,
189 + Type: module.Line,
190 + Dims: module.Dims{
191 + {ID: "input_voltage", Name: "voltage", Div: precision},
192 + {ID: "input_voltage_min", Name: "min_voltage", Div: precision},
193 + {ID: "input_voltage_max", Name: "max_voltage", Div: precision},
194 + },
195 + }
196 + inputFrequencyChart = module.Chart{
197 + ID: "ups_input_frequency",
198 + Title: "UPS Input Frequency",
199 + Units: "Hz",
200 + Fam: "input",
201 + Ctx: "apcupsd.ups_input_frequency",
202 + Priority: prioUpsInputFrequency,
203 + Type: module.Line,
204 + Dims: module.Dims{
205 + {ID: "input_frequency", Name: "frequency", Div: precision},
206 + },
207 + }
208 +)
209 +
210 +// Output
211 +var (
212 + outputVoltageChart = module.Chart{
213 + ID: "ups_output_voltage",
214 + Title: "UPS Output Voltage",
215 + Units: "Volts",
216 + Fam: "output",
217 + Ctx: "apcupsd.ups_output_voltage",
218 + Priority: prioUpsOutputVoltage,
219 + Type: module.Line,
220 + Dims: module.Dims{
221 + {ID: "output_voltage", Name: "voltage", Div: precision},
222 + },
223 + }
224 +)
src/go/plugin/go.d/modules/apcupsd/client.go new
+115
@@ -0,0 +1,115 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package apcupsd
4 +
5 +import (
6 + "bytes"
7 + "encoding/binary"
8 + "io"
9 + "net"
10 + "time"
11 +)
12 +
13 +type apcupsdConn interface {
14 + connect() error
15 + disconnect() error
16 + status() ([]byte, error)
17 +}
18 +
19 +func newUpsdConn(conf Config) apcupsdConn {
20 + return &apcupsdClient{
21 + address: conf.Address,
22 + timeout: conf.Timeout.Duration(),
23 + }
24 +}
25 +
26 +type apcupsdClient struct {
27 + address string
28 + timeout time.Duration
29 + conn net.Conn
30 +}
31 +
32 +func (c *apcupsdClient) connect() error {
33 + if c.conn != nil {
34 + _ = c.disconnect()
35 + }
36 +
37 + conn, err := net.DialTimeout("tcp", c.address, c.timeout)
38 + if err != nil {
39 + return err
40 + }
41 +
42 + c.conn = conn
43 +
44 + return nil
45 +}
46 +
47 +func (c *apcupsdClient) disconnect() error {
48 + if c.conn != nil {
49 + err := c.conn.Close()
50 + c.conn = nil
51 + return err
52 + }
53 + return nil
54 +}
55 +
56 +func (c *apcupsdClient) status() ([]byte, error) {
57 + if err := c.send("status"); err != nil {
58 + return nil, err
59 + }
60 + return c.receive()
61 +}
62 +
63 +func (c *apcupsdClient) send(cmd string) error {
64 + // https://github.com/therealbstern/apcupsd/blob/224d19d5faa508d04267f6135fe53d50800550de/src/lib/apclibnis.c#L153
65 +
66 + msgLength := make([]byte, 2)
67 +
68 + binary.BigEndian.PutUint16(msgLength, uint16(len(cmd)))
69 +
70 + if err := c.conn.SetWriteDeadline(c.deadline()); err != nil {
71 + return err
72 + }
73 +
74 + if _, err := c.conn.Write(append(msgLength, cmd...)); err != nil {
75 + return err
76 + }
77 +
78 + return nil
79 +}
80 +
81 +func (c *apcupsdClient) receive() ([]byte, error) {
82 + // https://github.com/therealbstern/apcupsd/blob/224d19d5faa508d04267f6135fe53d50800550de/src/apcnis.c#L54
83 +
84 + var buf bytes.Buffer
85 + msgLength := make([]byte, 2)
86 +
87 + for {
88 + if err := c.conn.SetReadDeadline(c.deadline()); err != nil {
89 + return nil, err
90 + }
91 +
92 + if _, err := io.ReadFull(c.conn, msgLength); err != nil {
93 + return nil, err
94 + }
95 +
96 + length := binary.BigEndian.Uint16(msgLength)
97 + if length == 0 {
98 + break
99 + }
100 +
101 + if err := c.conn.SetReadDeadline(c.deadline()); err != nil {
102 + return nil, err
103 + }
104 +
105 + if _, err := io.CopyN(&buf, c.conn, int64(length)); err != nil {
106 + return nil, err
107 + }
108 + }
109 +
110 + return buf.Bytes(), nil
111 +}
112 +
113 +func (c *apcupsdClient) deadline() time.Time {
114 + return time.Now().Add(c.timeout)
115 +}
src/go/plugin/go.d/modules/apcupsd/collect.go new
+144
@@ -0,0 +1,144 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package apcupsd
4 +
5 +import (
6 + "errors"
7 + "fmt"
8 + "strings"
9 + "time"
10 +)
11 +
12 +const precision = 100
13 +
14 +func (a *Apcupsd) collect() (map[string]int64, error) {
15 + if a.conn == nil {
16 + conn, err := a.establishConnection()
17 + if err != nil {
18 + return nil, err
19 + }
20 + a.conn = conn
21 + }
22 +
23 + resp, err := a.conn.status()
24 + if err != nil {
25 + a.Cleanup()
26 + return nil, err
27 + }
28 +
29 + mx := make(map[string]int64)
30 +
31 + if err := a.collectStatus(mx, resp); err != nil {
32 + return nil, err
33 + }
34 +
35 + return mx, nil
36 +}
37 +
38 +func (a *Apcupsd) collectStatus(mx map[string]int64, resp []byte) error {
39 + st, err := parseStatus(resp)
40 + if err != nil {
41 + return fmt.Errorf("failed to parse status: %v", err)
42 + }
43 +
44 + if st.status == "" {
45 + return errors.New("unexpected response: status is empty")
46 + }
47 +
48 + for _, v := range upsStatuses {
49 + mx["status_"+v] = 0
50 + }
51 + for _, v := range strings.Fields(st.status) {
52 + mx["status_"+v] = 1
53 + }
54 +
55 + switch st.status {
56 + case "COMMLOST", "SHUTTING_DOWN":
57 + return nil
58 + }
59 +
60 + if st.selftest != "" {
61 + for _, v := range upsSelftestStatuses {
62 + mx["selftest_"+v] = 0
63 + }
64 + mx["selftest_"+st.selftest] = 1
65 + }
66 +
67 + if st.bcharge != nil {
68 + mx["battery_charge"] = int64(*st.bcharge * precision)
69 + }
70 + if st.battv != nil {
71 + mx["battery_voltage"] = int64(*st.battv * precision)
72 + }
73 + if st.nombattv != nil {
74 + mx["battery_voltage_nominal"] = int64(*st.nombattv * precision)
75 + }
76 + if st.linev != nil {
77 + mx["input_voltage"] = int64(*st.linev * precision)
78 + }
79 + if st.minlinev != nil {
80 + mx["input_voltage_min"] = int64(*st.minlinev * precision)
81 + }
82 + if st.maxlinev != nil {
83 + mx["input_voltage_max"] = int64(*st.maxlinev * precision)
84 + }
85 + if st.linefreq != nil {
86 + mx["input_frequency"] = int64(*st.linefreq * precision)
87 + }
88 + if st.outputv != nil {
89 + mx["output_voltage"] = int64(*st.outputv * precision)
90 + }
91 + if st.nomoutv != nil {
92 + mx["output_voltage_nominal"] = int64(*st.nomoutv * precision)
93 + }
94 + if st.loadpct != nil {
95 + mx["load_percent"] = int64(*st.loadpct * precision)
96 + }
97 + if st.itemp != nil {
98 + mx["itemp"] = int64(*st.itemp * precision)
99 + }
100 + if st.timeleft != nil {
101 + mx["timeleft"] = int64(*st.timeleft * 60 * precision) // to seconds
102 + }
103 + if st.nompower != nil && st.loadpct != nil {
104 + mx["load"] = int64(*st.nompower * *st.loadpct / 100)
105 + }
106 + if st.battdate != "" {
107 + if v, err := battdateSecondsAgo(st.battdate); err != nil {
108 + a.Debugf("failed to calculate time since battery replacement for date '%s': %v", st.battdate, err)
109 + } else {
110 + mx["battery_seconds_since_replacement"] = v
111 + }
112 + }
113 +
114 + return nil
115 +}
116 +
117 +func battdateSecondsAgo(battdate string) (int64, error) {
118 + var layout string
119 +
120 + if strings.ContainsRune(battdate, '-') {
121 + layout = "2006-01-02"
122 + } else {
123 + layout = "01/02/06"
124 + }
125 +
126 + date, err := time.Parse(layout, battdate)
127 + if err != nil {
128 + return 0, err
129 + }
130 +
131 + secsAgo := int64(time.Now().Sub(date).Seconds())
132 +
133 + return secsAgo, nil
134 +}
135 +
136 +func (a *Apcupsd) establishConnection() (apcupsdConn, error) {
137 + conn := a.newConn(a.Config)
138 +
139 + if err := conn.connect(); err != nil {
140 + return nil, err
141 + }
142 +
143 + return conn, nil
144 +}
src/go/plugin/go.d/modules/apcupsd/config_schema.json new
+44
@@ -0,0 +1,44 @@
1 +{
2 + "jsonSchema": {
3 + "$schema": "http://json-schema.org/draft-07/schema#",
4 + "title": "Apcupsd collector configuration.",
5 + "type": "object",
6 + "properties": {
7 + "update_every": {
8 + "title": "Update every",
9 + "description": "Data collection interval, measured in seconds.",
10 + "type": "integer",
11 + "minimum": 1,
12 + "default": 1
13 + },
14 + "address": {
15 + "title": "Address",
16 + "description": "The IP address and port where the Apcupsd daemon listens for connections.",
17 + "type": "string",
18 + "default": "127.0.0.1:3551"
19 + },
20 + "timeout": {
21 + "title": "Timeout",
22 + "description": "Timeout for establishing a connection and communication (reading and writing) in seconds.",
23 + "type": "number",
24 + "minimum": 0.5,
25 + "default": 1
26 + }
27 + },
28 + "required": [
29 + "address"
30 + ],
31 + "additionalProperties": false,
32 + "patternProperties": {
33 + "^name$": {}
34 + }
35 + },
36 + "uiSchema": {
37 + "uiOptions": {
38 + "fullPage": true
39 + },
40 + "timeout": {
41 + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
42 + }
43 + }
44 +}
src/go/plugin/go.d/modules/apcupsd/metadata.yaml new
+244
@@ -0,0 +1,244 @@
1 +plugin_name: go.d.plugin
2 +modules:
3 + - meta:
4 + id: collector-go.d.plugin-apcupsd
5 + plugin_name: go.d.plugin
6 + module_name: apcupsd
7 + monitored_instance:
8 + name: APC UPS
9 + link: https://www.apc.com
10 + icon_filename: apc.svg
11 + categories:
12 + - data-collection.ups
13 + keywords:
14 + - ups
15 + - apcupsd
16 + - apc
17 + related_resources:
18 + integrations:
19 + list: []
20 + info_provided_to_referring_integrations:
21 + description: ""
22 + most_popular: false
23 + overview:
24 + data_collection:
25 + metrics_description: |
26 + This collector monitors Uninterruptible Power Supplies by polling the Apcupsd daemon.
27 + method_description: ""
28 + supported_platforms:
29 + include: []
30 + exclude: []
31 + multi_instance: true
32 + additional_permissions:
33 + description: ""
34 + default_behavior:
35 + auto_detection:
36 + description: |
37 + By default, it detects Apcupsd instances running on localhost that are listening on port 3551.
38 + On startup, it tries to collect metrics from:
39 +
40 + - 127.0.0.1:3551
41 + limits:
42 + description: ""
43 + performance_impact:
44 + description: ""
45 + setup:
46 + prerequisites:
47 + list: []
48 + configuration:
49 + file:
50 + name: go.d/apcupsd.conf
51 + options:
52 + description: |
53 + The following options can be defined globally: update_every, autodetection_retry.
54 + folding:
55 + title: Config options
56 + enabled: true
57 + list:
58 + - name: update_every
59 + description: Data collection frequency.
60 + default_value: 1
61 + required: false
62 + - name: autodetection_retry
63 + description: Recheck interval in seconds. Zero means no recheck will be scheduled.
64 + default_value: 0
65 + required: false
66 + - name: address
67 + description: Apcupsd daemon address in IP:PORT format.
68 + default_value: 127.0.0.1:3551
69 + required: true
70 + - name: timeout
71 + description: Connection/read/write timeout in seconds. The timeout includes name resolution, if required.
72 + default_value: 2
73 + required: false
74 + examples:
75 + folding:
76 + title: Config
77 + enabled: true
78 + list:
79 + - name: Basic
80 + description: A basic example configuration.
81 + config: |
82 + jobs:
83 + - name: local
84 + address: 127.0.0.1:3551
85 + - name: Multi-instance
86 + description: |
87 + > **Note**: When you define multiple jobs, their names must be unique.
88 +
89 + Collecting metrics from local and remote instances.
90 + config: |
91 + jobs:
92 + - name: local
93 + address: 127.0.0.1:3551
94 +
95 + - name: remote
96 + address: 203.0.113.0:3551
97 + troubleshooting:
98 + problems:
99 + list: []
100 + alerts:
101 + - name: apcupsd_ups_load_capacity
102 + metric: apcupsd.ups_load_capacity_utilization
103 + info: "APC UPS average load over the last 10 minutes"
104 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
105 + - name: apcupsd_ups_battery_charge
106 + metric: apcupsd.ups_battery_charge
107 + info: "APC UPS average battery charge over the last minute"
108 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
109 + - name: apcupsd_last_collected_secs
110 + metric: apcupsd.ups_status
111 + info: "APC UPS number of seconds since the last successful data collection"
112 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
113 + - name: apcupsd_ups_selftest_warning
114 + metric: apcupsd.ups_selftest
115 + info: "APC UPS self-test failed due to insufficient battery capacity or due to overload"
116 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
117 + - name: apcupsd_ups_status_onbatt
118 + metric: apcupsd.ups_status
119 + info: "APC UPS has switched to battery power because the input power has failed"
120 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
121 + - name: apcupsd_ups_status_overload
122 + metric: apcupsd.ups_status
123 + info: "APC UPS is overloaded and cannot supply enough power to the load"
124 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
125 + - name: apcupsd_ups_status_lowbatt
126 + metric: apcupsd.ups_status
127 + info: "APC UPS battery is low and needs to be recharged"
128 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
129 + - name: apcupsd_ups_status_replacebatt
130 + metric: apcupsd.ups_status
131 + info: "APC UPS battery has reached the end of its lifespan and needs to be replaced"
132 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
133 + - name: apcupsd_ups_status_nobatt
134 + metric: apcupsd.ups_status
135 + info: "APC UPS has no battery"
136 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
137 + - name: apcupsd_ups_status_commlost
138 + metric: apcupsd.ups_status
139 + info: "APC UPS communication link is lost"
140 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/apcupsd.conf
141 + metrics:
142 + folding:
143 + title: Metrics
144 + enabled: false
145 + description: ""
146 + availability: []
147 + scopes:
148 + - name: ups
149 + description: These metrics refer to the UPS unit.
150 + labels: []
151 + metrics:
152 + - name: apcupsd.ups_status
153 + description: UPS Status
154 + unit: status
155 + chart_type: line
156 + dimensions:
157 + - name: TRIM
158 + - name: BOOST
159 + - name: CAL
160 + - name: ONLINE
161 + - name: ONBATT
162 + - name: OVERLOAD
163 + - name: LOWBATT
164 + - name: REPLACEBATT
165 + - name: NOBATT
166 + - name: SLAVE
167 + - name: SLAVEDOWN
168 + - name: COMMLOST
169 + - name: SHUTTING_DOWN
170 + - name: apcupsd.ups_selftest
171 + description: UPS Self-Test Status
172 + unit: status
173 + chart_type: line
174 + dimensions:
175 + - name: NO
176 + - name: NG
177 + - name: WN
178 + - name: IP
179 + - name: OK
180 + - name: BT
181 + - name: UNK
182 + - name: apcupsd.ups_battery_charge
183 + description: UPS Battery Charge
184 + unit: percent
185 + chart_type: area
186 + dimensions:
187 + - name: charge
188 + - name: apcupsd.ups_battery_time_remaining
189 + description: UPS Estimated Runtime on Battery
190 + unit: seconds
191 + chart_type: line
192 + dimensions:
193 + - name: timeleft
194 + - name: apcupsd.ups_battery_time_since_replacement
195 + description: UPS Time Since Battery Replacement
196 + unit: seconds
197 + chart_type: line
198 + dimensions:
199 + - name: since_replacement
200 + - name: apcupsd.ups_battery_voltage
201 + description: UPS Battery Voltage
202 + unit: Volts
203 + chart_type: line
204 + dimensions:
205 + - name: voltage
206 + - name: nominal_voltage
207 + - name: apcupsd.ups_load_capacity_utilization
208 + description: UPS Load Capacity Utilization
209 + unit: percent
210 + chart_type: area
211 + dimensions:
212 + - name: load
213 + - name: apcupsd.ups_load
214 + description: UPS Load
215 + unit: Watts
216 + chart_type: line
217 + dimensions:
218 + - name: load
219 + - name: apcupsd.ups_temperature
220 + description: UPS Internal Temperature
221 + unit: Celsius
222 + chart_type: line
223 + dimensions:
224 + - name: temperature
225 + - name: apcupsd.ups_input_voltage
226 + description: UPS Input Voltage
227 + unit: Volts
228 + chart_type: line
229 + dimensions:
230 + - name: voltage
231 + - name: min_voltage
232 + - name: max_voltage
233 + - name: apcupsd.ups_input_frequency
234 + description: UPS Input Frequency
235 + unit: Hz
236 + chart_type: line
237 + dimensions:
238 + - name: frequency
239 + - name: apcupsd.ups_output_voltage
240 + description: UPS Output Voltage
241 + unit: Volts
242 + chart_type: line
243 + dimensions:
244 + - name: voltage
src/go/plugin/go.d/modules/apcupsd/status.go new
+137
@@ -0,0 +1,137 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package apcupsd
4 +
5 +import (
6 + "bufio"
7 + "bytes"
8 + "fmt"
9 + "strconv"
10 + "strings"
11 +)
12 +
13 +var upsStatuses = []string{
14 + "CAL",
15 + "TRIM",
16 + "BOOST",
17 + "ONLINE",
18 + "ONBATT",
19 + "OVERLOAD",
20 + "LOWBATT",
21 + "REPLACEBATT",
22 + "NOBATT",
23 + "SLAVE",
24 + "SLAVEDOWN",
25 + "COMMLOST",
26 + "SHUTTING_DOWN",
27 +}
28 +
29 +var upsSelftestStatuses = []string{
30 + "NO",
31 + "NG",
32 + "WN",
33 + "IP",
34 + "OK",
35 + "BT",
36 + "UNK",
37 +}
38 +
39 +// examples: https://github.com/therealbstern/apcupsd/tree/master/examples/status
40 +type apcupsdStatus struct {
41 + bcharge *float64 // battery charge level (percentage)
42 + battv *float64 // battery voltage (Volts)
43 + nombattv *float64 // nominal battery voltage (Volts)
44 + linev *float64 // line voltage (Volts)
45 + minlinev *float64 // min line voltage (Volts)
46 + maxlinev *float64 // max line voltage (Volts)
47 + linefreq *float64 // line frequency (Hz)
48 + outputv *float64 // output voltage (Volts)
49 + nomoutv *float64 // nominal output voltage (Volts)
50 + loadpct *float64 // UPS Load (Percent Load Capacity)
51 + itemp *float64 // internal UPS temperature (Celsius)
52 + nompower *float64 // nominal power (Watts)
53 + timeleft *float64 // estimated runtime left (minutes)
54 + battdate string // Last battery change date (MM/DD/YY or YYYY-MM-DD)
55 + status string
56 + selftest string
57 +}
58 +
59 +func parseStatus(resp []byte) (*apcupsdStatus, error) {
60 + var st apcupsdStatus
61 + sc := bufio.NewScanner(bytes.NewBuffer(resp))
62 +
63 + for sc.Scan() {
64 + line := sc.Text()
65 +
66 + key, value, ok := strings.Cut(line, ":")
67 + if !ok {
68 + continue
69 + }
70 +
71 + key, value = strings.TrimSpace(key), strings.TrimSpace(value)
72 +
73 + if value == "N/A" {
74 + continue
75 + }
76 +
77 + var err error
78 +
79 + // https://github.com/therealbstern/apcupsd/blob/224d19d5faa508d04267f6135fe53d50800550de/src/lib/apcstatus.c#L30
80 + switch key {
81 + case "BCHARGE":
82 + st.bcharge, err = parseFloat(value)
83 + case "BATTV":
84 + st.battv, err = parseFloat(value)
85 + case "NOMBATTV":
86 + st.nombattv, err = parseFloat(value)
87 + case "LINEV":
88 + st.linev, err = parseFloat(value)
89 + case "MINLINEV":
90 + st.minlinev, err = parseFloat(value)
91 + case "MAXLINEV":
92 + st.maxlinev, err = parseFloat(value)
93 + case "LINEFREQ":
94 + st.linefreq, err = parseFloat(value)
95 + case "OUTPUTV":
96 + st.outputv, err = parseFloat(value)
97 + case "NOMOUTV":
98 + st.nomoutv, err = parseFloat(value)
99 + case "LOADPCT":
100 + st.loadpct, err = parseFloat(value)
101 + case "ITEMP":
102 + st.itemp, err = parseFloat(value)
103 + case "NOMPOWER":
104 + st.nompower, err = parseFloat(value)
105 + case "TIMELEFT":
106 + st.timeleft, err = parseFloat(value)
107 + case "BATTDATE":
108 + st.battdate = value
109 + case "STATUS":
110 + if value == "SHUTTING DOWN" {
111 + value = "SHUTTING_DOWN"
112 + }
113 + st.status = value
114 + case "SELFTEST":
115 + if value == "??" {
116 + value = "UNK"
117 + }
118 + st.selftest = value
119 + default:
120 + continue
121 + }
122 + if err != nil {
123 + return nil, fmt.Errorf("line '%s': %v", line, err)
124 + }
125 + }
126 +
127 + return &st, nil
128 +}
129 +
130 +func parseFloat(s string) (*float64, error) {
131 + val, _, _ := strings.Cut(s, " ")
132 + f, err := strconv.ParseFloat(val, 64)
133 + if err != nil {
134 + return nil, err
135 + }
136 + return &f, nil
137 +}
src/go/plugin/go.d/modules/apcupsd/testdata/config.json new
+5
@@ -0,0 +1,5 @@
1 +{
2 + "update_every": 123,
3 + "address": "ok",
4 + "timeout": 123.123
5 +}
src/go/plugin/go.d/modules/apcupsd/testdata/config.yaml new
+3
@@ -0,0 +1,3 @@
1 +update_every: 123
2 +address: "ok"
3 +timeout: 123.123
src/go/plugin/go.d/modules/apcupsd/testdata/status.txt new
+56
@@ -0,0 +1,56 @@
1 +DATE : Wed Sep 27 17:30:23 CEST 2000
2 +HOSTNAME : test
3 +RELEASE : 3.7.3-20000925
4 +CABLE : Custom Cable Smart
5 +MODEL : SMART-UPS 1000
6 +UPSMODE : Stand Alone
7 +STARTTIME: Wed Sep 27 10:39:23 CEST 2000
8 +UPSNAME : UPS_IDEN
9 +STATUS : ONLINE
10 +LINEV : 235.3 Volts
11 +LOADPCT : 9.3 Percent Load Capacity
12 +BCHARGE : 100.0 Percent
13 +TIMELEFT : 130.0 Minutes
14 +MBATTCHG : 5 Percent
15 +MINTIMEL : 3 Minutes
16 +MAXTIME : 0 Seconds
17 +MAXLINEV : 239.2 Volts
18 +MINLINEV : 234.0 Volts
19 +OUTPUTV : 236.6 Volts
20 +SENSE : High
21 +DWAKE : 000 Seconds
22 +DSHUTD : 020 Seconds
23 +DLOWBATT : 02 Minutes
24 +LOTRANS : 196.0 Volts
25 +HITRANS : 253.0 Volts
26 +RETPCT : 000.0 Percent
27 +ITEMP : 32.8 C Internal
28 +ALARMDEL : 5 seconds
29 +BATTV : 27.9 Volts
30 +LINEFREQ : 50.0 Hz
31 +LASTXFER : Line voltage notch or spike
32 +NUMXFERS : 0
33 +XONBATT : N/A
34 +TONBATT : 0 seconds
35 +CUMONBATT: 0 seconds
36 +XOFFBATT : N/A
37 +SELFTEST : NO
38 +STESTI : 336
39 +STATFLAG : 0x08 Status Flag
40 +DIPSW : 0x00 Dip Switch
41 +REG1 : 0x00 Register 1
42 +REG2 : 0x00 Register 2
43 +REG3 : 0x00 Register 3
44 +MANDATE : 07/31/99
45 +SERIALNO : QS9931125245
46 +BATTDATE : 07/31/99
47 +NOMOUTV : 230
48 +NOMBATTV : 24.0
49 +NOMPOWER : 600 Watts
50 +HUMIDITY : N/A
51 +AMBTEMP : N/A
52 +EXTBATTS : 0
53 +BADBATTS : N/A
54 +FIRMWARE : 60.11.I
55 +APCMODEL : IWI
56 +END APC : Wed Sep 27 17:30:31 CEST 2000
src/go/plugin/go.d/modules/apcupsd/testdata/status_commlost.txt new
+18
@@ -0,0 +1,18 @@
1 +APC : 001,017,0427
2 +DATE : 2024-09-06 20:22:06 +0300
3 +HOSTNAME : test
4 +VERSION : 3.14.14 (31 May 2016) debian
5 +CABLE : USB Cable
6 +DRIVER : USB UPS Driver
7 +UPSMODE : Stand Alone
8 +STARTTIME: 2024-09-03 10:15:36 +0300
9 +STATUS : COMMLOST
10 +MBATTCHG : 5 Percent
11 +MINTIMEL : 3 Minutes
12 +MAXTIME : 0 Seconds
13 +NUMXFERS : 0
14 +TONBATT : 0 Seconds
15 +CUMONBATT: 0 Seconds
16 +XOFFBATT : N/A
17 +STATFLAG : 0x05000100
18 +END APC : 2024-09-07 19:26:34 +0300
src/go/plugin/go.d/modules/init.go
+1
@@ -7,6 +7,7 @@ import (
7 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/adaptecraid"
8 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/ap"
9 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/apache"
10 + _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/apcupsd"
11 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/beanstalk"
12 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/bind"
13 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/boinc"
src/health/health.d/apcupsd.conf
+23 -25
@@ -1,11 +1,11 @@
1 # you can disable an alarm notification by setting the 'to' line to: silent
2
3 - template: apcupsd_10min_ups_load
4 - on: apcupsd.load
3 + template: apcupsd_ups_load_capacity
4 + on: apcupsd.ups_load_capacity_utilization
5 class: Utilization
6 type: Power Supply
7 -component: UPS
8 - lookup: average -10m unaligned of percentage
7 +component: UPS device
8 + lookup: average -10m unaligned of load
9 units: %
10 every: 1m
11 warn: $this > (($status >= $WARNING) ? (70) : (80))
@@ -14,13 +14,11 @@ component: UPS
14 info: APC UPS average load over the last 10 minutes
15 to: sitemgr
16
17 -# Discussion in https://github.com/netdata/netdata/pull/3928:
18 -# Fire the alarm as soon as it's going on battery (99% charge) and clear only when full.
19 - template: apcupsd_ups_charge
20 - on: apcupsd.charge
17 + template: apcupsd_ups_battery_charge
18 + on: apcupsd.ups_battery_charge
19 class: Errors
20 type: Power Supply
23 -component: UPS
21 +component: UPS device
22 lookup: average -60s unaligned of charge
23 units: %
24 every: 60s
@@ -32,7 +30,7 @@ component: UPS
30 to: sitemgr
31
32 template: apcupsd_last_collected_secs
35 - on: apcupsd.load
33 + on: apcupsd.ups_status
34 class: Latency
35 type: Power Supply
36 component: UPS device
@@ -47,21 +45,21 @@ component: UPS device
45
46 #Send out a warning when SELFTEST code is BT or NG. Code descriptions can be found at:
47 #http://www.apcupsd.org/manual/#:~:text=or%20N/A.-,SELFTEST,-The%20results%20of
50 - template: apcupsd_selftest_warning
51 - on: apcupsd.selftest
48 + template: apcupsd_ups_selftest_warning
49 + on: apcupsd.ups_selftest
50 lookup: max -1s unaligned match-names of BT,NG
51 units: status
52 every: 10s
53 warn: $this == 1
54 delay: up 0 down 15m multiplier 1.5 max 1h
57 - info: APC UPS self-test failed due to insufficient battery capacity or due to overload.
55 + info: APC UPS self-test failed due to insufficient battery capacity or due to overload
56 to: sitemgr
57
58 #Send out a warning when STATUS code is ONBATT,OVERLOAD,LOWBATT,REPLACEBATT,NOBATT,COMMLOST
59 #https://man.archlinux.org/man/apcaccess.8.en#:~:text=apcupsd%20was%20started-,STATUS,-%3A%20UPS%20status.%20One
60
63 - template: apcupsd_status_onbatt
64 - on: apcupsd.status
61 + template: apcupsd_ups_status_onbatt
62 + on: apcupsd.ups_status
63 lookup: max -1s unaligned match-names of ONBATT
64 units: status
65 every: 10s
@@ -70,8 +68,8 @@ component: UPS device
68 info: APC UPS has switched to battery power because the input power has failed
69 to: sitemgr
70
73 - template: apcupsd_status_overload
74 - on: apcupsd.status
71 + template: apcupsd_ups_status_overload
72 + on: apcupsd.ups_status
73 lookup: max -1s unaligned match-names of OVERLOAD
74 units: status
75 every: 10s
@@ -80,8 +78,8 @@ component: UPS device
78 info: APC UPS is overloaded and cannot supply enough power to the load
79 to: sitemgr
80
83 - template: apcupsd_status_lowbatt
84 - on: apcupsd.status
81 + template: apcupsd_ups_status_lowbatt
82 + on: apcupsd.ups_status
83 lookup: max -1s unaligned match-names of LOWBATT
84 units: status
85 every: 10s
@@ -90,8 +88,8 @@ component: UPS device
88 info: APC UPS battery is low and needs to be recharged
89 to: sitemgr
90
93 - template: apcupsd_status_replacebatt
94 - on: apcupsd.status
91 + template: apcupsd_ups_status_replacebatt
92 + on: apcupsd.ups_status
93 lookup: max -1s unaligned match-names of REPLACEBATT
94 units: status
95 every: 10s
@@ -100,8 +98,8 @@ component: UPS device
98 info: APC UPS battery has reached the end of its lifespan and needs to be replaced
99 to: sitemgr
100
103 - template: apcupsd_status_nobatt
104 - on: apcupsd.status
101 + template: apcupsd_ups_status_nobatt
102 + on: apcupsd.ups_status
103 lookup: max -1s unaligned match-names of NOBATT
104 units: status
105 every: 10s
@@ -110,8 +108,8 @@ component: UPS device
108 info: APC UPS has no battery
109 to: sitemgr
110
113 - template: apcupsd_status_commlost
114 - on: apcupsd.status
111 + template: apcupsd_ups_status_commlost
112 + on: apcupsd.ups_status
113 lookup: max -1s unaligned match-names of COMMLOST
114 units: status
115 every: 10s