-
Notifications
You must be signed in to change notification settings - Fork 22
/
status.go
398 lines (362 loc) · 11.3 KB
/
status.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
package apcupsd
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
)
const (
// timeFormatLong is the package time format of long timestamps from a NIS.
timeFormatLong = "2006-01-02 15:04:05 -0700"
)
var (
// errInvalidKeyValuePair is returned when a message is not in the expected
// "key : value" format.
errInvalidKeyValuePair = errors.New("invalid key/value pair")
// errInvalidDuration is returned when a value is not in the expected
// duration format, e.g. "10 Seconds" or "2 minutes".
errInvalidDuration = errors.New("invalid time duration")
)
// Status is the status of an APC Uninterruptible Power Supply (UPS), as
// returned by a NIS.
type Status struct {
// Header record indicating the STATUS format revision level, the number of records that follow the
// APC statement, and the number of bytes that follow the record.
APC string
// The date and time that the information was last obtained from the UPS.
Date time.Time
// The name of the machine that collected the UPS data.
Hostname string
// The apcupsd release number, build date, and platform.
Version string
// The name of the UPS as stored in the EEPROM or in the UPSNAME directive in the configuration file.
UPSName string
// The cable as specified in the configuration file (UPSCABLE).
Cable string
// The driver being used to communicate with the UPS.
Driver string
// The mode in which apcupsd is operating as specified in the configuration file (UPSMODE)
UPSMode string
// The time/date that apcupsd was started.
StartTime time.Time
// The UPS model as derived from information from the UPS.
Model string
// The current status of the UPS (ONLINE, ONBATT, etc.)
Status string
// The current line voltage as returned by the UPS.
LineVoltage float64
// The percentage of load capacity as estimated by the UPS.
LoadPercent float64
// The percentage charge on the batteries.
BatteryChargePercent float64
// The remaining runtime left on batteries as estimated by the UPS.
TimeLeft time.Duration
// If the battery charge percentage (BCHARGE) drops below this value, apcupsd will shutdown your
// system. Value is set in the configuration file (BATTERYLEVEL)
MinimumBatteryChargePercent float64
// apcupsd will shutdown your system if the remaining runtime equals or is below this point. Value is set
// in the configuration file (MINUTES)
MinimumTimeLeft time.Duration
// apcupsd will shutdown your system if the time on batteries exceeds this value. A value of zero
// disables the feature. Value is set in the configuration file (TIMEOUT)
MaximumTime time.Duration
// The sensitivity level of the UPS to line voltage fluctuations.
Sense string
// The line voltage below which the UPS will switch to batteries.
LowTransferVoltage float64
// The line voltage above which the UPS will switch to batteries.
HighTransferVoltage float64
// The delay period for the UPS alarm.
AlarmDel time.Duration
// Battery voltage as supplied by the UPS.
BatteryVoltage float64
// The reason for the last transfer to batteries.
LastTransfer string
// The number of transfers to batteries since apcupsd startup.
NumberTransfers int
// Time and date of last transfer to batteries, or N/A.
XOnBattery time.Time
// Time in seconds currently on batteries, or 0.
TimeOnBattery time.Duration
// Total (cumulative) time on batteries in seconds since apcupsd startup.
CumulativeTimeOnBattery time.Duration
// Time and date of last transfer from batteries, or N/A.
XOffBattery time.Time
// The interval in hours between automatic self tests.
LastSelftest time.Time
// The results of the last self test, and may have the following values:
// • OK: self test indicates good battery
// • BT: self test failed due to insufficient battery capacity
// • NG: self test failed due to overload
// • NO: No results (i.e. no self test performed in the last 5 minutes)
Selftest bool
// Status flag. English version is given by STATUS.
StatusFlags string
// The UPS serial number
SerialNumber string
// The date that batteries were last replaced
BatteryDate string
// The input voltage that the UPS is configured to expect.
NominalInputVoltage float64
// The nominal battery voltage.
NominalBatteryVoltage float64
// The maximum power in Watts that the UPS is designed to supply.
NominalPower int
// The firmware revision number as reported by the UPS.
Firmware string
// The time and date that the STATUS record was written.
EndAPC time.Time
// The ambient temperature as measured by the UPS.
InternalTemp float64
OutputVoltage float64
LineFrequency float64
OutputAmps float64
}
// parseKV parses an input key/value string in "key : value" format, and sets
// the appropriate struct field from the input data.
func (s *Status) parseKV(kv string) error {
sp := strings.SplitN(kv, ":", 2)
if len(sp) != 2 {
return errInvalidKeyValuePair
}
var (
k = key(strings.TrimSpace(sp[0]))
v = strings.TrimSpace(sp[1])
)
// Attempt to match various common data types.
if match := s.parseKVString(k, v); match {
return nil
}
if match, err := s.parseKVFloat(k, v); match {
return err
}
if match, err := s.parseKVTime(k, v); match {
return err
}
if match, err := s.parseKVDuration(k, v); match {
return err
}
// Attempt to match uncommon data types.
var err error
switch k {
case keyNumXfers:
s.NumberTransfers, err = strconv.Atoi(v)
case keyNomPower:
f := strings.SplitN(v, " ", 2)
s.NominalPower, err = strconv.Atoi(f[0])
case keySelftest:
s.Selftest = v == "YES"
}
return err
}
// TODO(mdlayher): rework parsing code and add enumcheck.
// A key is a field key for an apcupsd status line.
type key string
// List of keys sent by a NIS, used to map values to Status fields.
const (
keyAlarmDel key = "ALARMDEL"
keyAPC key = "APC"
keyBattDate key = "BATTDATE"
keyBattV key = "BATTV"
keyBCharge key = "BCHARGE"
keyCable key = "CABLE"
keyCumOnBatt key = "CUMONBATT"
keyDate key = "DATE"
keyDriver key = "DRIVER"
keyEndAPC key = "END APC"
keyFirmware key = "FIRMWARE"
keyHiTrans key = "HITRANS"
keyHostname key = "HOSTNAME"
keyITemp key = "ITEMP"
keyLastStest key = "LASTSTEST"
keyLastXfer key = "LASTXFER"
keyLineFrequency key = "LINEFREQ"
keyLineV key = "LINEV"
keyLoadPct key = "LOADPCT"
keyLoTrans key = "LOTRANS"
keyMaxTime key = "MAXTIME"
keyMBattChg key = "MBATTCHG"
keyMinTimeL key = "MINTIMEL"
keyModel key = "MODEL"
keyNomBattV key = "NOMBATTV"
keyNomInV key = "NOMINV"
keyNomPower key = "NOMPOWER"
keyNumXfers key = "NUMXFERS"
keyOutV key = "OUTPUTV"
keyOutputAmps key = "OUTCURNT"
keySelftest key = "SELFTEST"
keySense key = "SENSE"
keySerialNo key = "SERIALNO"
keyStartTime key = "STARTTIME"
keyStatFlag key = "STATFLAG"
keyStatus key = "STATUS"
keyTimeLeft key = "TIMELEFT"
keyTOnBatt key = "TONBATT"
keyUPSMode key = "UPSMODE"
keyUPSName key = "UPSNAME"
keyVersion key = "VERSION"
keyXOffBat key = "XOFFBATT"
keyXOnBat key = "XONBATT"
)
// parseKVString parses a simple string into the appropriate Status field. It
// returns true if a field was matched, and false if not.
func (s *Status) parseKVString(k key, v string) bool {
switch k {
case keyAPC:
s.APC = v
case keyHostname:
s.Hostname = v
case keyVersion:
s.Version = v
case keyUPSName:
s.UPSName = v
case keyCable:
s.Cable = v
case keyDriver:
s.Driver = v
case keyUPSMode:
s.UPSMode = v
case keyModel:
s.Model = v
case keyStatus:
s.Status = v
case keySense:
s.Sense = v
case keyLastXfer:
s.LastTransfer = v
case keyStatFlag:
s.StatusFlags = v
case keySerialNo:
s.SerialNumber = v
case keyBattDate:
s.BatteryDate = v
case keyFirmware:
s.Firmware = v
default:
return false
}
return true
}
// parseKVFloat parses a float64 value into the appropriate Status field. It
// returns true if a field was matched, and false if not.
func (s *Status) parseKVFloat(k key, v string) (bool, error) {
f := strings.SplitN(v, " ", 2)
// Save repetition for function calls.
parse := func() (float64, error) {
return strconv.ParseFloat(f[0], 64)
}
var err error
switch k {
case keyLineV:
s.LineVoltage, err = parse()
case keyLoadPct:
s.LoadPercent, err = parse()
case keyBCharge:
s.BatteryChargePercent, err = parse()
case keyMBattChg:
s.MinimumBatteryChargePercent, err = parse()
case keyLoTrans:
s.LowTransferVoltage, err = parse()
case keyHiTrans:
s.HighTransferVoltage, err = parse()
case keyBattV:
s.BatteryVoltage, err = parse()
case keyNomInV:
s.NominalInputVoltage, err = parse()
case keyNomBattV:
s.NominalBatteryVoltage, err = parse()
case keyITemp:
s.InternalTemp, err = parse()
case keyOutV:
s.OutputVoltage, err = parse()
case keyLineFrequency:
s.LineFrequency, err = parse()
case keyOutputAmps:
s.OutputAmps, err = parse()
default:
return false, nil
}
return true, err
}
// parseKVTime parses a time.Time value into the appropriate Status field. It
// returns true if a field was matched, and false if not.
func (s *Status) parseKVTime(k key, v string) (bool, error) {
var err error
switch k {
case keyDate:
s.Date, err = parseOptionalTime(v)
case keyStartTime:
s.StartTime, err = parseOptionalTime(v)
case keyXOnBat:
s.XOnBattery, err = parseOptionalTime(v)
case keyXOffBat:
s.XOffBattery, err = parseOptionalTime(v)
case keyLastStest:
s.LastSelftest, err = parseOptionalTime(v)
case keyEndAPC:
s.EndAPC, err = parseOptionalTime(v)
default:
return false, nil
}
return true, err
}
// parseKVDuration parses a time.Duration into the appropriate Status field. It
// returns true if a field was matched, and false if not.
func (s *Status) parseKVDuration(k key, v string) (bool, error) {
// Save repetition for function calls.
parse := func() (time.Duration, error) {
return parseDuration(v)
}
var err error
switch k {
case keyTimeLeft:
s.TimeLeft, err = parse()
case keyMinTimeL:
s.MinimumTimeLeft, err = parse()
case keyMaxTime:
s.MaximumTime, err = parse()
case keyAlarmDel:
// This field can take a variety of formats, so just ignore any error.
s.AlarmDel, _ = parse()
return true, nil
case keyTOnBatt:
s.TimeOnBattery, err = parse()
case keyCumOnBatt:
s.CumulativeTimeOnBattery, err = parse()
default:
return false, nil
}
return true, err
}
// parseDuration parses a duration value returned from a NIS as a time.Duration.
func parseDuration(d string) (time.Duration, error) {
ss := strings.SplitN(d, " ", 2)
if len(ss) != 2 {
return 0, errInvalidDuration
}
var (
num = ss[0]
unit = ss[1]
)
// Normalize units into ones that time.ParseDuration expects.
switch strings.ToLower(unit) {
case "minutes":
unit = "m"
case "seconds":
unit = "s"
}
return time.ParseDuration(fmt.Sprintf("%s%s", num, unit))
}
// parseOptionalTime parses a time string but also accepts the special value
// "N/A" (which apcupsd reports for some values and conditions); this value is
// mapped to time.Time{}. The caller can check for this with time.IsZero().
func parseOptionalTime(value string) (time.Time, error) {
if value == "N/A" {
return time.Time{}, nil
}
if time, err := time.Parse(timeFormatLong, value); err == nil {
return time, nil
}
return time.Time{}, fmt.Errorf("can't parse time: %q", value)
}