Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
305
plugins/inputs/modbus/request.go
Normal file
305
plugins/inputs/modbus/request.go
Normal file
|
@ -0,0 +1,305 @@
|
|||
package modbus
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
)
|
||||
|
||||
type request struct {
|
||||
address uint16
|
||||
length uint16
|
||||
fields []field
|
||||
}
|
||||
|
||||
func countRegisters(requests []request) uint64 {
|
||||
var l uint64
|
||||
for _, r := range requests {
|
||||
l += uint64(r.length)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Only split too-large groups, but ignore all optimization potential
|
||||
func splitMaxBatchSize(g request, maxBatchSize uint16) []request {
|
||||
var requests []request
|
||||
|
||||
idx := 0
|
||||
for start := g.address; start < g.address+g.length; {
|
||||
current := request{
|
||||
address: start,
|
||||
}
|
||||
|
||||
// Initialize the end to a safe value avoiding infinite loops
|
||||
end := g.address + g.length
|
||||
var batchEnd uint16
|
||||
if start >= math.MaxUint16-maxBatchSize {
|
||||
batchEnd = math.MaxUint16
|
||||
} else {
|
||||
batchEnd = start + maxBatchSize
|
||||
}
|
||||
for _, f := range g.fields[idx:] {
|
||||
// If the current field exceeds the batch size we need to split
|
||||
// the request here
|
||||
if f.address+f.length > batchEnd {
|
||||
break
|
||||
}
|
||||
// End of field still fits into the batch so add it to the request
|
||||
current.fields = append(current.fields, f)
|
||||
end = f.address + f.length
|
||||
idx++
|
||||
}
|
||||
|
||||
if end > g.address+g.length {
|
||||
end = g.address + g.length
|
||||
}
|
||||
if idx >= len(g.fields) || g.fields[idx].address >= end {
|
||||
current.length = end - start
|
||||
} else {
|
||||
current.length = g.fields[idx].address - start
|
||||
}
|
||||
start = end
|
||||
|
||||
if len(current.fields) > 0 {
|
||||
requests = append(requests, current)
|
||||
}
|
||||
}
|
||||
|
||||
return requests
|
||||
}
|
||||
|
||||
func shrinkGroup(g request, maxBatchSize uint16) []request {
|
||||
var requests []request
|
||||
var current request
|
||||
|
||||
for _, f := range g.fields {
|
||||
// Just add the field and update length if we are still
|
||||
// within the maximum batch-size
|
||||
if current.length > 0 && f.address+f.length <= current.address+maxBatchSize {
|
||||
current.fields = append(current.fields, f)
|
||||
current.length = f.address - current.address + f.length
|
||||
continue
|
||||
}
|
||||
|
||||
// Ignore completely empty requests
|
||||
if len(current.fields) > 0 {
|
||||
requests = append(requests, current)
|
||||
}
|
||||
|
||||
// Create a new request
|
||||
current = request{
|
||||
fields: []field{f},
|
||||
address: f.address,
|
||||
length: f.length,
|
||||
}
|
||||
}
|
||||
if len(current.fields) > 0 {
|
||||
requests = append(requests, current)
|
||||
}
|
||||
|
||||
return requests
|
||||
}
|
||||
|
||||
func optimizeGroup(g request, maxBatchSize uint16) []request {
|
||||
if len(g.fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
requests := shrinkGroup(g, maxBatchSize)
|
||||
length := countRegisters(requests)
|
||||
|
||||
for i := 1; i < len(g.fields)-1; i++ {
|
||||
// Always keep consecutive fields as they are known to be optimal
|
||||
if g.fields[i-1].address+g.fields[i-1].length == g.fields[i].address {
|
||||
continue
|
||||
}
|
||||
|
||||
// Perform the split and check if it is better
|
||||
// Note: This involves recursive optimization of the right side of the split.
|
||||
current := shrinkGroup(request{fields: g.fields[:i]}, maxBatchSize)
|
||||
current = append(current, optimizeGroup(request{fields: g.fields[i:]}, maxBatchSize)...)
|
||||
currentLength := countRegisters(current)
|
||||
|
||||
// Do not allow for more requests
|
||||
if len(current) > len(requests) {
|
||||
continue
|
||||
}
|
||||
// Try to reduce the number of registers we are trying to access
|
||||
if currentLength >= length {
|
||||
continue
|
||||
}
|
||||
|
||||
// We found a better solution
|
||||
requests = current
|
||||
length = currentLength
|
||||
}
|
||||
|
||||
return requests
|
||||
}
|
||||
|
||||
func optimizeGroupWithinLimits(g request, params groupingParams) []request {
|
||||
if len(g.fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var requests []request
|
||||
currentRequest := request{
|
||||
fields: []field{g.fields[0]},
|
||||
address: g.fields[0].address,
|
||||
length: g.fields[0].length,
|
||||
}
|
||||
for i := 1; i <= len(g.fields)-1; i++ {
|
||||
// Check if we need to interrupt the current chunk and require a new one
|
||||
holeSize := g.fields[i].address - (g.fields[i-1].address + g.fields[i-1].length)
|
||||
if g.fields[i].address < g.fields[i-1].address+g.fields[i-1].length {
|
||||
params.log.Warnf(
|
||||
"Request at %d with length %d overlaps with next request at %d",
|
||||
g.fields[i-1].address, g.fields[i-1].length, g.fields[i].address,
|
||||
)
|
||||
holeSize = 0
|
||||
}
|
||||
needInterrupt := holeSize > params.maxExtraRegisters // too far apart
|
||||
needInterrupt = needInterrupt || currentRequest.length+holeSize+g.fields[i].length > params.maxBatchSize // too large
|
||||
if !needInterrupt {
|
||||
// Still safe to add the field to the current request
|
||||
currentRequest.length = g.fields[i].address + g.fields[i].length - currentRequest.address
|
||||
currentRequest.fields = append(currentRequest.fields, g.fields[i])
|
||||
continue
|
||||
}
|
||||
// Finish the current request, add it to the list and construct a new one
|
||||
requests = append(requests, currentRequest)
|
||||
currentRequest = request{
|
||||
fields: []field{g.fields[i]},
|
||||
address: g.fields[i].address,
|
||||
length: g.fields[i].length,
|
||||
}
|
||||
}
|
||||
requests = append(requests, currentRequest)
|
||||
return requests
|
||||
}
|
||||
|
||||
type groupingParams struct {
|
||||
// Maximum size of a request in registers
|
||||
maxBatchSize uint16
|
||||
// optimization to use for grouping register groups to requests, Also put potential optimization parameters here
|
||||
optimization string
|
||||
maxExtraRegisters uint16
|
||||
// Will force reads to start at zero (if possible) while respecting the max-batch size.
|
||||
enforceFromZero bool
|
||||
// tags to add for the requests
|
||||
tags map[string]string
|
||||
// log facility to inform the user
|
||||
log telegraf.Logger
|
||||
}
|
||||
|
||||
func groupFieldsToRequests(fields []field, params groupingParams) []request {
|
||||
if len(fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sort the fields by address (ascending) and length
|
||||
sort.Slice(fields, func(i, j int) bool {
|
||||
addrI := fields[i].address
|
||||
addrJ := fields[j].address
|
||||
return addrI < addrJ || (addrI == addrJ && fields[i].length > fields[j].length)
|
||||
})
|
||||
|
||||
// Construct the consecutive register chunks for the addresses and construct Modbus requests.
|
||||
// For field addresses like [1, 2, 3, 5, 6, 10, 11, 12, 14] we should construct the following
|
||||
// requests (1, 3) , (5, 2) , (10, 3), (14 , 1). Furthermore, we should respect field boundaries
|
||||
// and the given maximum chunk sizes.
|
||||
var groups []request
|
||||
var current request
|
||||
for _, f := range fields {
|
||||
// Add tags from higher up
|
||||
if f.tags == nil {
|
||||
f.tags = make(map[string]string, len(params.tags))
|
||||
}
|
||||
for k, v := range params.tags {
|
||||
f.tags[k] = v
|
||||
}
|
||||
|
||||
// Check if we need to interrupt the current chunk and require a new one
|
||||
if current.length > 0 && f.address == current.address+current.length {
|
||||
// Still safe to add the field to the current request
|
||||
current.length += f.length
|
||||
if !f.omit {
|
||||
current.fields = append(current.fields, f)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Finish the current request, add it to the list and construct a new one
|
||||
if current.length > 0 && len(fields) > 0 {
|
||||
groups = append(groups, current)
|
||||
}
|
||||
current = request{
|
||||
address: f.address,
|
||||
length: f.length,
|
||||
}
|
||||
if !f.omit {
|
||||
current.fields = append(current.fields, f)
|
||||
}
|
||||
}
|
||||
if current.length > 0 && len(fields) > 0 {
|
||||
groups = append(groups, current)
|
||||
}
|
||||
|
||||
if len(groups) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enforce the first read to start at zero if the option is set
|
||||
if params.enforceFromZero {
|
||||
groups[0].length += groups[0].address
|
||||
groups[0].address = 0
|
||||
}
|
||||
|
||||
var requests []request
|
||||
switch params.optimization {
|
||||
case "shrink":
|
||||
// Shrink request by striping leading and trailing fields with an omit flag set
|
||||
for _, g := range groups {
|
||||
if len(g.fields) > 0 {
|
||||
requests = append(requests, shrinkGroup(g, params.maxBatchSize)...)
|
||||
}
|
||||
}
|
||||
case "rearrange":
|
||||
// Allow rearranging fields between request in order to reduce the number of touched
|
||||
// registers while keeping the number of requests
|
||||
for _, g := range groups {
|
||||
if len(g.fields) > 0 {
|
||||
requests = append(requests, optimizeGroup(g, params.maxBatchSize)...)
|
||||
}
|
||||
}
|
||||
case "aggressive":
|
||||
// Allow rearranging fields similar to "rearrange" but allow mixing of groups
|
||||
// This might reduce the number of requests at the cost of more registers being touched.
|
||||
var total request
|
||||
for _, g := range groups {
|
||||
if len(g.fields) > 0 {
|
||||
total.fields = append(total.fields, g.fields...)
|
||||
}
|
||||
}
|
||||
requests = optimizeGroup(total, params.maxBatchSize)
|
||||
case "max_insert":
|
||||
// Similar to aggressive but keeps the number of touched registers below a threshold
|
||||
var total request
|
||||
for _, g := range groups {
|
||||
if len(g.fields) > 0 {
|
||||
total.fields = append(total.fields, g.fields...)
|
||||
}
|
||||
}
|
||||
requests = optimizeGroupWithinLimits(total, params)
|
||||
default:
|
||||
// no optimization
|
||||
for _, g := range groups {
|
||||
if len(g.fields) > 0 {
|
||||
requests = append(requests, splitMaxBatchSize(g, params.maxBatchSize)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requests
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue