diff --git a/plugins/inputs/upsd/README.md b/plugins/inputs/upsd/README.md index 0ad2e9c01bfdf..7ff659bb95f8c 100644 --- a/plugins/inputs/upsd/README.md +++ b/plugins/inputs/upsd/README.md @@ -35,6 +35,16 @@ plugin ordering. See [CONFIGURATION.md][CONFIGURATION.md] for more details. ## parsed as integers and others as floats. # force_float = false + ## Force vendor/product IDs to always be emitted as strings to avoid type + ## conflicts between UPS devices with numeric-looking IDs (e.g. "0764", + ## auto-converted to int64 by the NUT client library) and UPS devices with + ## non-numeric IDs (e.g. "ABCD"). Affected fields: ups_vendorid, ups_productid, + ## driver_parameter_vendorid, driver_parameter_productid. + ## Note: leading zeros are lost on the wire (e.g. "0764" becomes "764"). + ## If left unset, a warning is logged. The default will change to true in a + ## future release. + # stringify_ids = false + ## Collect additional fields if they are available for the UPS ## The fields need to be specified as NUT variable names, see ## https://networkupstools.org/docs/developer-guide.chunked/apas02.html @@ -71,6 +81,24 @@ Alternatively, you can also map the non-binary value to a `boolean`. [enum_processor]: /plugins/processors/enum/README.md +### Vendor/Product ID types (`stringify_ids`) + +The underlying NUT client library (`go.nut`) auto-detects numeric-looking values +and converts them to `int64`. This means a `vendorid` like `"0764"` becomes +`int64(764)` while a non-numeric `vendorid` like `"ABCD"` stays a string. When +multiple UPS devices of different vendors write to the same InfluxDB bucket, +this causes field type conflicts on `ups_vendorid`, `ups_productid`, +`driver_parameter_vendorid` and `driver_parameter_productid`. + +Set `stringify_ids = true` to force these four fields to always be emitted as +strings. The default is currently `false` to preserve backwards-compatible +behavior, but will flip to `true` in a future release. If the option is left +unset, a warning is logged on startup. + +> **Note:** the NUT library parses `"0764"` into `int64(764)` before Telegraf +> sees it, so the stringified value will be `"764"` — leading zeros are lost +> and cannot be recovered at this layer. + ## Metrics This implementation tries to maintain compatibility with the apcupsd metrics: diff --git a/plugins/inputs/upsd/sample.conf b/plugins/inputs/upsd/sample.conf index 3ec797338550f..285530b9bee6a 100644 --- a/plugins/inputs/upsd/sample.conf +++ b/plugins/inputs/upsd/sample.conf @@ -13,6 +13,16 @@ ## parsed as integers and others as floats. # force_float = false + ## Force vendor/product IDs to always be emitted as strings to avoid type + ## conflicts between UPS devices with numeric-looking IDs (e.g. "0764", + ## auto-converted to int64 by the NUT client library) and UPS devices with + ## non-numeric IDs (e.g. "ABCD"). Affected fields: ups_vendorid, ups_productid, + ## driver_parameter_vendorid, driver_parameter_productid. + ## Note: leading zeros are lost on the wire (e.g. "0764" becomes "764"). + ## If left unset, a warning is logged. The default will change to true in a + ## future release. + # stringify_ids = false + ## Collect additional fields if they are available for the UPS ## The fields need to be specified as NUT variable names, see ## https://networkupstools.org/docs/developer-guide.chunked/apas02.html diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/expected.out b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/expected.out new file mode 100644 index 0000000000000..2694c664c9468 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/expected.out @@ -0,0 +1 @@ +upsd,model=CP900EPFCLCD,serial=0,status_OL=true,ups_name=fake battery_charge_low=10i,battery_charge_percent=100i,battery_charge_warning=20i,battery_mfr_date="CPS",battery_runtime=4020i,battery_runtime_low=300i,battery_type="PbAcid",battery_voltage=24,device_mfr="CPS",device_model="CP900EPFCLCD",device_serial=0i,device_type="ups",driver_debug=0i,driver_flag_allow_killpower=0i,driver_name="usbhid-ups",driver_parameter_pollfreq=30i,driver_parameter_pollinterval=2i,driver_parameter_port="auto",driver_parameter_product="CP900EPFCLCD",driver_parameter_productid="ABCD",driver_parameter_serial=0i,driver_parameter_synchronous="auto",driver_parameter_vendor="CPS",driver_parameter_vendorid="EFGH",driver_state="quiet",driver_version="2.8.1",driver_version_data="CyberPower HID 0.8",driver_version_internal=0.52,driver_version_usb="libusb-1.0.26 (API: 0x1000109)",firmware="",input_transfer_high=260i,input_transfer_low=170i,input_voltage=228,load_percent=13i,nominal_battery_voltage=24i,nominal_input_voltage=230i,nominal_power=540i,output_voltage=228,status_flags=8u,time_left_ns=4020000000000i,ups_beeper_status=true,ups_delay_shutdown=20i,ups_delay_start=30i,ups_mfr="CPS",ups_model="CP900EPFCLCD",ups_productid="WXYZ",ups_serial=0i,ups_status="OL",ups_test_result="No test initiated",ups_timer_shutdown=-60i,ups_timer_start=-60i,ups_vendorid="IJKL" diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/telegraf.conf b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/telegraf.conf new file mode 100644 index 0000000000000..5fed247df9155 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/telegraf.conf @@ -0,0 +1,2 @@ +[[inputs.upsd]] + additional_fields = ["*"] diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/types.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/types.dev new file mode 100644 index 0000000000000..e2adc1ceb0458 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/types.dev @@ -0,0 +1,49 @@ +battery.charge: NUMBER +battery.charge.low: STRING +battery.charge.warning: NUMBER +battery.mfr.date: NUMBER +battery.runtime: NUMBER +battery.runtime.low: STRING +battery.type: NUMBER +battery.voltage: NUMBER +battery.voltage.nominal: NUMBER +device.mfr: NUMBER +device.model: NUMBER +device.serial: NUMBER +device.type: NUMBER +driver.debug: NUMBER +driver.flag.allow_killpower: NUMBER +driver.name: NUMBER +driver.parameter.pollfreq: NUMBER +driver.parameter.pollinterval: NUMBER +driver.parameter.port: NUMBER +driver.parameter.product: NUMBER +driver.parameter.productid: NUMBER +driver.parameter.serial: NUMBER +driver.parameter.synchronous: NUMBER +driver.parameter.vendor: NUMBER +driver.parameter.vendorid: NUMBER +driver.state: NUMBER +driver.version: +driver.version.data: NUMBER +driver.version.internal: NUMBER +driver.version.usb: NUMBER +input.transfer.high: STRING +input.transfer.low: STRING +input.voltage: NUMBER +input.voltage.nominal: NUMBER +output.voltage: NUMBER +ups.beeper.status: NUMBER +ups.delay.shutdown: STRING +ups.delay.start: STRING +ups.load: NUMBER +ups.mfr: NUMBER +ups.model: NUMBER +ups.productid: NUMBER +ups.realpower.nominal: NUMBER +ups.serial: NUMBER +ups.status: NUMBER +ups.test.result: NUMBER +ups.timer.shutdown: NUMBER +ups.timer.start: NUMBER +ups.vendorid: NUMBER diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/variables.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/variables.dev new file mode 100644 index 0000000000000..e09d8d6cea377 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_mixed_ids/variables.dev @@ -0,0 +1,49 @@ +battery.charge: 100 +battery.charge.low: 10 +battery.charge.warning: 20 +battery.mfr.date: CPS +battery.runtime: 4020 +battery.runtime.low: 300 +battery.type: PbAcid +battery.voltage: 24.0 +battery.voltage.nominal: 24 +device.mfr: CPS +device.model: CP900EPFCLCD +device.serial: 000000000000 +device.type: ups +driver.debug: 0 +driver.flag.allow_killpower: 0 +driver.name: usbhid-ups +driver.parameter.pollfreq: 30 +driver.parameter.pollinterval: 2 +driver.parameter.port: auto +driver.parameter.product: CP900EPFCLCD +driver.parameter.productid: ABCD +driver.parameter.serial: 000000000000 +driver.parameter.synchronous: auto +driver.parameter.vendor: CPS +driver.parameter.vendorid: EFGH +driver.state: quiet +driver.version: 2.8.1 +driver.version.data: CyberPower HID 0.8 +driver.version.internal: 0.52 +driver.version.usb: libusb-1.0.26 (API: 0x1000109) +input.transfer.high: 260 +input.transfer.low: 170 +input.voltage: 228.0 +input.voltage.nominal: 230 +output.voltage: 228.0 +ups.beeper.status: enabled +ups.delay.shutdown: 20 +ups.delay.start: 30 +ups.load: 13 +ups.mfr: CPS +ups.model: CP900EPFCLCD +ups.productid: WXYZ +ups.realpower.nominal: 540 +ups.serial: 000000000000 +ups.status: OL +ups.test.result: No test initiated +ups.timer.shutdown: -60 +ups.timer.start: -60 +ups.vendorid: IJKL diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/expected.out b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/expected.out new file mode 100644 index 0000000000000..4b539a9fa2d8b --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/expected.out @@ -0,0 +1 @@ +upsd,model=CP900EPFCLCD,serial=0,status_OL=true,ups_name=fake battery_charge_low=10i,battery_charge_percent=100i,battery_charge_warning=20i,battery_mfr_date="CPS",battery_runtime=4020i,battery_runtime_low=300i,battery_type="PbAcid",battery_voltage=24,device_mfr="CPS",device_model="CP900EPFCLCD",device_serial=0i,device_type="ups",driver_debug=0i,driver_flag_allow_killpower=0i,driver_name="usbhid-ups",driver_parameter_pollfreq=30i,driver_parameter_pollinterval=2i,driver_parameter_port="auto",driver_parameter_product="CP900EPFCLCD",driver_parameter_productid="501",driver_parameter_serial=0i,driver_parameter_synchronous="auto",driver_parameter_vendor="CPS",driver_parameter_vendorid="764",driver_state="quiet",driver_version="2.8.1",driver_version_data="CyberPower HID 0.8",driver_version_internal=0.52,driver_version_usb="libusb-1.0.26 (API: 0x1000109)",firmware="",input_transfer_high=260i,input_transfer_low=170i,input_voltage=228,load_percent=13i,nominal_battery_voltage=24i,nominal_input_voltage=230i,nominal_power=540i,output_voltage=228,status_flags=8u,time_left_ns=4020000000000i,ups_beeper_status=true,ups_delay_shutdown=20i,ups_delay_start=30i,ups_mfr="CPS",ups_model="CP900EPFCLCD",ups_productid="501",ups_serial=0i,ups_status="OL",ups_test_result="No test initiated",ups_timer_shutdown=-60i,ups_timer_start=-60i,ups_vendorid="764" diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/telegraf.conf b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/telegraf.conf new file mode 100644 index 0000000000000..5aa4566f9922f --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/telegraf.conf @@ -0,0 +1,3 @@ +[[inputs.upsd]] + additional_fields = ["*"] + stringify_ids = true diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/types.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/types.dev new file mode 100644 index 0000000000000..2f47bac6d96a5 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/types.dev @@ -0,0 +1,49 @@ +battery.charge: NUMBER +battery.charge.low: STRING +battery.charge.warning: NUMBER +battery.mfr.date: NUMBER +battery.runtime: NUMBER +battery.runtime.low: STRING +battery.type: NUMBER +battery.voltage: NUMBER +battery.voltage.nominal: NUMBER +device.mfr: NUMBER +device.model: NUMBER +device.serial: NUMBER +device.type: NUMBER +driver.debug: NUMBER +driver.flag.allow_killpower: NUMBER +driver.name: NUMBER +driver.parameter.pollfreq: NUMBER +driver.parameter.pollinterval: NUMBER +driver.parameter.port: NUMBER +driver.parameter.product: NUMBER +driver.parameter.productid: NUMBER +driver.parameter.serial: NUMBER +driver.parameter.synchronous: NUMBER +driver.parameter.vendor: NUMBER +driver.parameter.vendorid: NUMBER +driver.state: NUMBER +driver.version: +driver.version.data: NUMBER +driver.version.internal: NUMBER +driver.version.usb: NUMBER +input.transfer.high: STRING +input.transfer.low: STRING +input.voltage: NUMBER +input.voltage.nominal: NUMBER +output.voltage: NUMBER +ups.beeper.status: NUMBER +ups.delay.shutdown: STRING +ups.delay.start: STRING +ups.load: NUMBER +ups.mfr: NUMBER +ups.model: NUMBER +ups.productid: NUMBER +ups.realpower.nominal: NUMBER +ups.serial: NUMBER +ups.status: NUMBER +ups.test.result: NUMBER +ups.timer.shutdown: NUMBER +ups.timer.start: NUMBER +ups.vendorid: NUMBER diff --git a/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/variables.dev b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/variables.dev new file mode 100644 index 0000000000000..1ee4e943f16c4 --- /dev/null +++ b/plugins/inputs/upsd/testcases/CyberPowerSystems_CP900EPFCLCD_full_stringify_ids/variables.dev @@ -0,0 +1,49 @@ +battery.charge: 100 +battery.charge.low: 10 +battery.charge.warning: 20 +battery.mfr.date: CPS +battery.runtime: 4020 +battery.runtime.low: 300 +battery.type: PbAcid +battery.voltage: 24.0 +battery.voltage.nominal: 24 +device.mfr: CPS +device.model: CP900EPFCLCD +device.serial: 000000000000 +device.type: ups +driver.debug: 0 +driver.flag.allow_killpower: 0 +driver.name: usbhid-ups +driver.parameter.pollfreq: 30 +driver.parameter.pollinterval: 2 +driver.parameter.port: auto +driver.parameter.product: CP900EPFCLCD +driver.parameter.productid: 0501 +driver.parameter.serial: 000000000000 +driver.parameter.synchronous: auto +driver.parameter.vendor: CPS +driver.parameter.vendorid: 0764 +driver.state: quiet +driver.version: 2.8.1 +driver.version.data: CyberPower HID 0.8 +driver.version.internal: 0.52 +driver.version.usb: libusb-1.0.26 (API: 0x1000109) +input.transfer.high: 260 +input.transfer.low: 170 +input.voltage: 228.0 +input.voltage.nominal: 230 +output.voltage: 228.0 +ups.beeper.status: enabled +ups.delay.shutdown: 20 +ups.delay.start: 30 +ups.load: 13 +ups.mfr: CPS +ups.model: CP900EPFCLCD +ups.productid: 0501 +ups.realpower.nominal: 540 +ups.serial: 000000000000 +ups.status: OL +ups.test.result: No test initiated +ups.timer.shutdown: -60 +ups.timer.start: -60 +ups.vendorid: 0764 diff --git a/plugins/inputs/upsd/upsd.go b/plugins/inputs/upsd/upsd.go index 760f75150d792..a5c40fa3459cc 100644 --- a/plugins/inputs/upsd/upsd.go +++ b/plugins/inputs/upsd/upsd.go @@ -19,6 +19,17 @@ import ( var sampleConfig string var ( + // Fields that must always be emitted as strings to avoid type conflicts. + // The go.nut library auto-detects numeric-looking values (e.g. "0764") + // and converts them to int64, but IDs like vendorid/productid should + // remain strings regardless of their content. + stringFieldSet = map[string]bool{ + "ups.vendorid": true, + "ups.productid": true, + "driver.parameter.vendorid": true, + "driver.parameter.productid": true, + } + // Define the default field set to add if existing defaultFieldSet = map[string]string{ "battery.charge": "battery_charge_percent", @@ -46,14 +57,15 @@ const ( ) type Upsd struct { - Server string `toml:"server"` - Port int `toml:"port"` - Username string `toml:"username"` - Password string `toml:"password"` - ForceFloat bool `toml:"force_float"` - Additional []string `toml:"additional_fields"` - DumpRaw bool `toml:"dump_raw_variables" deprecated:"1.35.0;use 'log_level' 'trace' instead"` - Log telegraf.Logger `toml:"-"` + Server string `toml:"server"` + Port int `toml:"port"` + Username string `toml:"username"` + Password string `toml:"password"` + ForceFloat bool `toml:"force_float"` + StringifyIDs *bool `toml:"stringify_ids"` + Additional []string `toml:"additional_fields"` + DumpRaw bool `toml:"dump_raw_variables" deprecated:"1.35.0;use 'log_level' 'trace' instead"` + Log telegraf.Logger `toml:"-"` filter filter.Filter dumped map[string]bool @@ -73,6 +85,13 @@ func (u *Upsd) Init() error { u.dumped = make(map[string]bool) + if u.StringifyIDs == nil { + u.Log.Warn("'stringify_ids' is not set explicitly; vendor/product IDs may be emitted as int64 " + + "when the underlying value looks numeric, causing type conflicts between UPS devices. " + + "The default will change to 'true' in a future release. " + + "Set 'stringify_ids = true' to opt into the new behavior now, or 'stringify_ids = false' to keep the legacy behavior and silence this warning.") + } + return nil } @@ -168,8 +187,16 @@ func (u *Upsd) gatherUps(acc telegraf.Accumulator, upsname string, variables []n continue } - // Force expected float values to actually being float (e.g. if delivered as int) - if u.ForceFloat { + // Force ID fields to always be strings to avoid type conflicts + // between UPS devices with numeric-looking IDs (auto-converted to + // int64 by go.nut) and devices with non-numeric IDs. + if u.StringifyIDs != nil && *u.StringifyIDs && stringFieldSet[varname] { + str, err := internal.ToString(v) + if err == nil { + v = str + } + } else if u.ForceFloat { + // Force expected float values to actually being float (e.g. if delivered as int) float, err := internal.ToFloat64(v) if err == nil { v = float diff --git a/plugins/inputs/upsd/upsd_test.go b/plugins/inputs/upsd/upsd_test.go index 32896d2e60f0f..e8680bfe6004e 100644 --- a/plugins/inputs/upsd/upsd_test.go +++ b/plugins/inputs/upsd/upsd_test.go @@ -33,6 +33,7 @@ func TestBadServer(t *testing.T) { plugin := &Upsd{ Server: addr.IP.String(), Port: addr.Port, + Log: testutil.Logger{}, } require.NoError(t, plugin.Init())