-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(input): add upsd implementation #9890
Merged
Merged
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
9df94af
feat(input): add basic upsd implementation
Malinskiy 03eaed6
fix(upsd): fix conversion from s to ns
Malinskiy af259b0
feat(upsd): add tests
Malinskiy 8488455
feat(upsd): simplify
Malinskiy 4f96a9b
feat(upsd): gather metrics concurrently
ash2k a7d47e1
feat(upsd): address review feedback
Malinskiy 156c8c5
Merge branch 'master' into feature/upsd
Malinskiy 6e0897b
feat(upsd): address review feedback
Malinskiy c0208b4
fix(upsd): add "ups.status" field to the test expectation
Malinskiy 50066f1
fix(upsd): add status tags and address style feedback
Malinskiy a6a50c9
fix(upsd): split tests
Malinskiy 35b7a2c
Merge branch 'master' into feature/upsd
Malinskiy 383e200
feat(upsd): remove timeout handling and migrate to the unpatched upst…
Malinskiy 79af9bc
fix(upsd): fix readme linter
Malinskiy 8b45ca1
fix(upsd): fix LICENSE_OF_DEPENDENCIES.md
Malinskiy 5e25dc1
fix(upsd): more markdown linter fixes
Malinskiy f37865c
fix(upsd): return deferred error during disconnect
Malinskiy a55f15b
fix(upsd): code linting fix
Malinskiy cc16ad1
fix(upsd): fix readme
Malinskiy 7d92070
fix(upsd): field ups.status -> ups_status
Malinskiy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# UPSD Input Plugin | ||
|
||
This plugin reads data of one or more Uninterruptible Power Supplies | ||
from an upsd daemon using its NUT network protocol. | ||
|
||
## Requirements | ||
|
||
upsd should be installed and it's daemon should be running. | ||
|
||
## Configuration | ||
|
||
```toml | ||
[[inputs.upsd]] | ||
## A running NUT server to connect to. | ||
# If not provided will default to 127.0.0.1 | ||
# server = "127.0.0.1" | ||
|
||
## The default NUT port 3493 can be overridden with: | ||
# port = 3493 | ||
|
||
# username = "user" | ||
# password = "password" | ||
``` | ||
|
||
## Metrics | ||
|
||
This implementation tries to maintain compatibility with the apcupsd metrics: | ||
|
||
- upsd | ||
- tags: | ||
- serial | ||
- ups_name | ||
- model | ||
- fields: | ||
- status_flags ([status-bits][]) | ||
- input_voltage | ||
- load_percent | ||
- battery_charge_percent | ||
- time_left_ns | ||
- output_voltage | ||
- internal_temp | ||
- battery_voltage | ||
- input_frequency | ||
- battery_date | ||
- nominal_input_voltage | ||
- nominal_battery_voltage | ||
- nominal_power | ||
- firmware | ||
|
||
With the exception of: | ||
|
||
- tags: | ||
- status (string representing the set status_flags) | ||
- fields: | ||
- time_on_battery_ns | ||
|
||
## Example Output | ||
|
||
```shell | ||
upsd,serial=AS1231515,ups_name=name1 load_percent=9.7,time_left_ns=9800000,output_voltage=230.4,internal_temp=32.4,battery_voltage=27.4,input_frequency=50.2,input_voltage=230.4,battery_charge_percent=100,status_flags=8i 1490035922000000000 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package upsd | ||
|
||
import ( | ||
"fmt" | ||
"github.com/influxdata/telegraf" | ||
"github.com/influxdata/telegraf/internal/choice" | ||
"github.com/influxdata/telegraf/plugins/inputs" | ||
nut "github.com/robbiet480/go.nut" | ||
"strings" | ||
) | ||
|
||
//See: https://networkupstools.org/docs/developer-guide.chunked/index.html | ||
|
||
const defaultAddress = "127.0.0.1" | ||
const defaultPort = 3493 | ||
|
||
type Upsd struct { | ||
Server string | ||
Port int | ||
Username string | ||
Password string | ||
Log telegraf.Logger `toml:"-"` | ||
|
||
batteryRuntimeTypeWarningIssued bool | ||
} | ||
|
||
func (*Upsd) Description() string { | ||
return "Monitor UPSes connected via Network UPS Tools" | ||
} | ||
|
||
var sampleConfig = ` | ||
## A running NUT server to connect to. | ||
# server = "127.0.0.1" | ||
# port = 3493 | ||
# username = "user" | ||
# password = "password" | ||
` | ||
|
||
func (*Upsd) SampleConfig() string { | ||
return sampleConfig | ||
} | ||
|
||
func (u *Upsd) Gather(acc telegraf.Accumulator) error { | ||
upsList, err := u.fetchVariables(u.Server, u.Port) | ||
if err != nil { | ||
return err | ||
} | ||
for name, variables := range upsList { | ||
u.gatherUps(acc, name, variables) | ||
} | ||
return nil | ||
} | ||
|
||
func (u *Upsd) gatherUps(acc telegraf.Accumulator, name string, variables []nut.Variable) { | ||
metrics := make(map[string]interface{}) | ||
for _, variable := range variables { | ||
name := variable.Name | ||
value := variable.Value | ||
metrics[name] = value | ||
} | ||
|
||
tags := map[string]string{ | ||
"serial": fmt.Sprintf("%v", metrics["device.serial"]), | ||
"ups_name": name, | ||
//"variables": variables.Status not sure if it's a good idea to provide this | ||
"model": fmt.Sprintf("%v", metrics["device.model"]), | ||
} | ||
|
||
// For compatibility with the apcupsd plugin's output we map the status string status into a bit-format | ||
status := u.mapStatus(metrics, tags) | ||
|
||
timeLeftS, ok := metrics["battery.runtime"].(int64) | ||
if !ok && !u.batteryRuntimeTypeWarningIssued { | ||
u.Log.Warnf("'battery.runtime' type is not int64") | ||
u.batteryRuntimeTypeWarningIssued = true | ||
} | ||
|
||
fields := map[string]interface{}{ | ||
"status_flags": status, | ||
"ups.status": metrics["ups.status"], | ||
srebhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"input_voltage": metrics["input.voltage"], | ||
"load_percent": metrics["ups.load"], | ||
"battery_charge_percent": metrics["battery.charge"], | ||
"time_left_ns": timeLeftS * 1_000_000_000, //Compatibility with apcupsd metrics format | ||
"output_voltage": metrics["output.voltage"], | ||
"internal_temp": metrics["ups.temperature"], | ||
"battery_voltage": metrics["battery.voltage"], | ||
"input_frequency": metrics["input.frequency"], | ||
"nominal_input_voltage": metrics["input.voltage.nominal"], | ||
"nominal_battery_voltage": metrics["battery.voltage.nominal"], | ||
"nominal_power": metrics["ups.realpower.nominal"], | ||
"firmware": metrics["ups.firmware"], | ||
"battery_date": metrics["battery.mfr.date"], | ||
} | ||
|
||
acc.AddFields("upsd", fields, tags) | ||
} | ||
|
||
func (u *Upsd) mapStatus(metrics map[string]interface{}, tags map[string]string) uint64 { | ||
status := uint64(0) | ||
statusString := fmt.Sprintf("%v", metrics["ups.status"]) | ||
statuses := strings.Fields(statusString) | ||
//Source: 1.3.2 at http://rogerprice.org/NUT/ConfigExamples.A5.pdf | ||
//apcupsd bits: | ||
//0 Runtime calibration occurring (Not reported by Smart UPS v/s and BackUPS Pro) | ||
//1 SmartTrim (Not reported by 1st and 2nd generation SmartUPS models) | ||
//2 SmartBoost | ||
//3 On line (this is the normal condition) | ||
//4 On battery | ||
//5 Overloaded output | ||
//6 Battery low | ||
//7 Replace battery | ||
if choice.Contains("CAL", statuses) { | ||
status |= 1 << 0 | ||
tags["status_CAL"] = "true" | ||
} | ||
if choice.Contains("TRIM", statuses) { | ||
status |= 1 << 1 | ||
tags["status_TRIM"] = "true" | ||
} | ||
if choice.Contains("BOOST", statuses) { | ||
status |= 1 << 2 | ||
tags["status_BOOST"] = "true" | ||
} | ||
if choice.Contains("OL", statuses) { | ||
status |= 1 << 3 | ||
tags["status_OL"] = "true" | ||
} | ||
if choice.Contains("OB", statuses) { | ||
status |= 1 << 4 | ||
tags["status_OB"] = "true" | ||
} | ||
if choice.Contains("OVER", statuses) { | ||
status |= 1 << 5 | ||
tags["status_OVER"] = "true" | ||
} | ||
if choice.Contains("LB", statuses) { | ||
status |= 1 << 6 | ||
tags["status_LB"] = "true" | ||
} | ||
if choice.Contains("RB", statuses) { | ||
status |= 1 << 7 | ||
tags["status_RB"] = "true" | ||
} | ||
return status | ||
} | ||
|
||
func (u *Upsd) fetchVariables(server string, port int) (map[string][]nut.Variable, error) { | ||
client, err := nut.Connect(server, port) | ||
if err != nil { | ||
return nil, fmt.Errorf("connect: %w", err) | ||
} | ||
|
||
if u.Username != "" && u.Password != "" { | ||
_, err = client.Authenticate(u.Username, u.Password) | ||
if err != nil { | ||
return nil, fmt.Errorf("auth: %w", err) | ||
} | ||
} | ||
|
||
upsList, err := client.GetUPSList() | ||
if err != nil { | ||
return nil, fmt.Errorf("getupslist: %w", err) | ||
} | ||
|
||
defer func() { | ||
_, disconnectErr := client.Disconnect() | ||
if disconnectErr != nil { | ||
err = fmt.Errorf("disconnect: %w", disconnectErr) | ||
} | ||
}() | ||
|
||
result := make(map[string][]nut.Variable) | ||
for _, ups := range upsList { | ||
result[ups.Name] = ups.Variables | ||
} | ||
|
||
return result, err | ||
} | ||
|
||
func init() { | ||
inputs.Add("upsd", func() telegraf.Input { | ||
return &Upsd{ | ||
Server: defaultAddress, | ||
Port: defaultPort, | ||
} | ||
}) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What type does
metrics["device.model"]
usually have?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expected to be string, e.g. "Model 12345" and just to be sure
fmt.Sprintg
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't use
fmt.Sprintf
if not necessary. This way we will notice if the protocol changes (which should never happen actually).