Skip to content
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(inputs.modbus): Optimise grouped requests #11106

Merged
merged 21 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions plugins/inputs/modbus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,21 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
## | to reduce the number of requested registers by keeping
## | the number of requests.
## |---aggressive -- Rearrange request boundaries similar to "rearrange" but
## allow to request registers not specified by the user to
## fill gaps. This usually reduces the number of requests at the
## cost of more requested registers.
## | allow to request registers not specified by the user to
## | fill gaps. This usually reduces the number of requests at the
## | cost of more requested registers.
## |---max_insert -- Rearrange request keeping the number of extra fields below the value
## provided in "optimization_max_register_fill". It is not necessary to define 'omitted'
## fields as the optimisation will add such field only where needed.
# optimization = "none"

## Maximum number register the optimizer is allowed to insert between two fields to
## save requests.
## This option is only used for the 'max_insert' optimization strategy.
## NOTE: All omitted fields are ignored, so this option denotes the effective hole
## size to fill.
# optimization_max_register_fill = 50

## Field definitions
## Analog Variables, Input Registers and Holding Registers
## address - address of the register to query. For coil and discrete inputs this is the bit address.
Expand Down Expand Up @@ -397,6 +407,23 @@ interested in but want to minimize the number of requests sent to the device.
__Please note:__ This optimization might take long in case of many
non-consecutive, non-omitted fields!

##### `max_insert`

Fields are assigned to the same request as long as the hole between the fields
do not exceed the maximum fill size given in `optimization_max_register_fill`.
User-defined omitted fields are ignored and interpreted as holes, so the best
practice is to not manually insert omitted fields for this optimizer. This
allows to specify only actually used fields and let the optimizer figure out
the request organization which can dramatically improve query time. The
trade-off here is between the cost of reading additional registers trashed
later and the cost of many requests.

__Please note:__ The optimal value for `optimization_max_register_fill` depends
on the network and the queried device. It is hence recommended to test several
values and assess performance in order to find the best value. Use the
`--test --debug` flags to monitor how may requests are sent and the number of
touched registers.

#### Field definitions

Each `request` can contain a list of fields to collect from the modbus device.
Expand Down
2 changes: 1 addition & 1 deletion plugins/inputs/modbus/configuration_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (c *ConfigurationOriginal) initRequests(fieldDefs []fieldDefinition, maxQua
if err != nil {
return nil, err
}
return groupFieldsToRequests(fields, nil, maxQuantity, "none"), nil
return groupFieldsToRequests(fields, nil, maxQuantity, "none", 0), nil
}

func (c *ConfigurationOriginal) initFields(fieldDefs []fieldDefinition) ([]field, error) {
Expand Down
57 changes: 37 additions & 20 deletions plugins/inputs/modbus/configuration_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"errors"
"fmt"
"hash/maphash"

"github.com/influxdata/telegraf/internal/choice"
)

//go:embed sample_request.conf
Expand All @@ -23,13 +21,14 @@ type requestFieldDefinition struct {
}

type requestDefinition struct {
SlaveID byte `toml:"slave_id"`
ByteOrder string `toml:"byte_order"`
RegisterType string `toml:"register"`
Measurement string `toml:"measurement"`
Optimization string `toml:"optimization"`
Fields []requestFieldDefinition `toml:"fields"`
Tags map[string]string `toml:"tags"`
SlaveID byte `toml:"slave_id"`
ByteOrder string `toml:"byte_order"`
RegisterType string `toml:"register"`
Measurement string `toml:"measurement"`
Optimization string `toml:"optimization"`
MaxExtraRegisters uint16 `toml:"optimization_max_register_fill"`
Fields []requestFieldDefinition `toml:"fields"`
Tags map[string]string `toml:"tags"`
}

type ConfigurationPerRequest struct {
Expand All @@ -46,12 +45,6 @@ func (c *ConfigurationPerRequest) Check() error {
seenFields := make(map[uint64]bool)

for _, def := range c.Requests {
// Check for valid optimization
validOptimizations := []string{"", "none", "shrink", "rearrange", "aggressive"}
if !choice.Contains(def.Optimization, validOptimizations) {
return fmt.Errorf("unknown optimization %q", def.Optimization)
}

// Check byte order of the data
switch def.ByteOrder {
case "":
Expand All @@ -69,7 +62,31 @@ func (c *ConfigurationPerRequest) Check() error {
default:
return fmt.Errorf("unknown register-type %q", def.RegisterType)
}

// Check for valid optimization
switch def.Optimization {
case "", "none", "shrink", "rearrange", "aggressive":
case "max_insert":
switch def.RegisterType {
case "coil":
if def.MaxExtraRegisters <= 0 || def.MaxExtraRegisters > maxQuantityCoils {
return fmt.Errorf("optimization_max_register_fill has to be between 1 and %d", maxQuantityCoils)
}
case "discrete":
if def.MaxExtraRegisters <= 0 || def.MaxExtraRegisters > maxQuantityDiscreteInput {
return fmt.Errorf("optimization_max_register_fill has to be between 1 and %d", maxQuantityDiscreteInput)
}
case "holding":
if def.MaxExtraRegisters <= 0 || def.MaxExtraRegisters > maxQuantityHoldingRegisters {
return fmt.Errorf("optimization_max_register_fill has to be between 1 and %d", maxQuantityHoldingRegisters)
}
case "input":
if def.MaxExtraRegisters <= 0 || def.MaxExtraRegisters > maxQuantityInputRegisters {
return fmt.Errorf("optimization_max_register_fill has to be between 1 and %d", maxQuantityInputRegisters)
}
}
default:
return fmt.Errorf("unknown optimization %q", def.Optimization)
}
// Set the default for measurement if required
if def.Measurement == "" {
def.Measurement = "modbus"
Expand Down Expand Up @@ -167,28 +184,28 @@ func (c *ConfigurationPerRequest) Process() (map[byte]requestSet, error) {
if c.workarounds.OnRequestPerField {
maxQuantity = 1
}
requests := groupFieldsToRequests(fields, def.Tags, maxQuantity, def.Optimization)
requests := groupFieldsToRequests(fields, def.Tags, maxQuantity, def.Optimization, def.MaxExtraRegisters)
set.coil = append(set.coil, requests...)
case "discrete":
maxQuantity := maxQuantityDiscreteInput
if c.workarounds.OnRequestPerField {
maxQuantity = 1
}
requests := groupFieldsToRequests(fields, def.Tags, maxQuantity, def.Optimization)
requests := groupFieldsToRequests(fields, def.Tags, maxQuantity, def.Optimization, def.MaxExtraRegisters)
set.discrete = append(set.discrete, requests...)
case "holding":
maxQuantity := maxQuantityHoldingRegisters
if c.workarounds.OnRequestPerField {
maxQuantity = 1
}
requests := groupFieldsToRequests(fields, def.Tags, maxQuantity, def.Optimization)
requests := groupFieldsToRequests(fields, def.Tags, maxQuantity, def.Optimization, def.MaxExtraRegisters)
set.holding = append(set.holding, requests...)
case "input":
maxQuantity := maxQuantityInputRegisters
if c.workarounds.OnRequestPerField {
maxQuantity = 1
}
requests := groupFieldsToRequests(fields, def.Tags, maxQuantity, def.Optimization)
requests := groupFieldsToRequests(fields, def.Tags, maxQuantity, def.Optimization, def.MaxExtraRegisters)
set.input = append(set.input, requests...)
default:
return nil, fmt.Errorf("unknown register type %q", def.RegisterType)
Expand Down
28 changes: 28 additions & 0 deletions plugins/inputs/modbus/modbus.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,35 @@ func (m *Modbus) Init() error {
if err := m.initClient(); err != nil {
return fmt.Errorf("initializing client failed: %v", err)
}
for slaveID, rqs := range m.requests {
var nHoldingRegs, nInputsRegs, nDiscreteRegs, nCoilRegs uint16
var nHoldingFields, nInputsFields, nDiscreteFields, nCoilFields int

for _, r := range rqs.holding {
nHoldingRegs += r.length
nHoldingFields += len(r.fields)
}
for _, r := range rqs.input {
nInputsRegs += r.length
nInputsFields += len(r.fields)
}
for _, r := range rqs.discrete {
nDiscreteRegs += r.length
nDiscreteFields += len(r.fields)
}
for _, r := range rqs.coil {
nCoilRegs += r.length
nCoilFields += len(r.fields)
}
m.Log.Infof("Got %d request(s) touching %d holding registers for %d fields (slave %d)",
len(rqs.holding), nHoldingRegs, nHoldingFields, slaveID)
m.Log.Infof("Got %d request(s) touching %d inputs registers for %d fields (slave %d)",
len(rqs.input), nInputsRegs, nInputsFields, slaveID)
m.Log.Infof("Got %d request(s) touching %d discrete registers for %d fields (slave %d)",
len(rqs.discrete), nDiscreteRegs, nDiscreteFields, slaveID)
m.Log.Infof("Got %d request(s) touching %d coil registers for %d fields (slave %d)",
len(rqs.coil), nCoilRegs, nCoilFields, slaveID)
}
return nil
}

Expand Down
Loading