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
134
plugins/serializers/binary/README.md
Normal file
134
plugins/serializers/binary/README.md
Normal file
|
@ -0,0 +1,134 @@
|
|||
# Binary Serializer Plugin
|
||||
|
||||
The `binary` data format serializer serializes metrics into binary protocols using
|
||||
user-specified configurations.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
[[outputs.socket_writer]]
|
||||
address = "tcp://127.0.0.1:54000"
|
||||
metric_batch_size = 1
|
||||
|
||||
## Data format to consume.
|
||||
## Each data format has its own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
data_format = "binary"
|
||||
|
||||
## Specify the endianness of the data.
|
||||
## Available values are "little" (little-endian), "big" (big-endian) and "host",
|
||||
## where "host" means the same endianness as the machine running Telegraf.
|
||||
# endianness = "host"
|
||||
|
||||
## Definition of the message format and the serialized data.
|
||||
## Please note that you need to define all elements of the data in the
|
||||
## correct order.
|
||||
## An entry can have the following properties:
|
||||
## read_from -- Source of the data.
|
||||
## Can be "field", "tag", "time" or "name".
|
||||
## If omitted "field" is assumed.
|
||||
## name -- Name of the element (e.g. field or tag).
|
||||
## Can be omitted for "time" and "name".
|
||||
## data_format -- Target data-type of the entry. Can be "int8/16/32/64", "uint8/16/32/64",
|
||||
## "float32/64", "string".
|
||||
## In case of time, this can be any of "unix" (default), "unix_ms", "unix_us",
|
||||
## "unix_ns".
|
||||
## If original field type is different from the target type, the field will be converted
|
||||
## If loss of precision is possible, warning will be logged.
|
||||
## string_length -- Length of the string in bytes. Only used for "string" type.
|
||||
## string_terminator -- Terminator for strings. Only used for "string" type.
|
||||
## Valid values are "null", "0x00", "00", "0x01", etc.
|
||||
## If original string length is greater than "string_length" the string will
|
||||
## be truncated to have length of the `string + terminator = string_length`.
|
||||
## If original string length is smaller than "string_length" the string
|
||||
## will be padded with terminator to have length of "string_length". (e.g. "abcd\0\0\0\0\0")
|
||||
## Defaults to "null" for strings.
|
||||
entries = [
|
||||
{ read_from = "field", name = "addr_3", data_format="int16" },
|
||||
{ read_from = "field", name = "addr_2", data_format="int16" },
|
||||
{ read_from = "field", name = "addr_4_5", data_format="int32" },
|
||||
{ read_from = "field", name = "addr_6_7", data_format="float32" },
|
||||
{ read_from = "field", name = "addr_16_20", data_format="string", string_terminator = "null", string_length = 11 },
|
||||
{ read_from = "field", name = "addr_3_sc", data_format="float64" }
|
||||
]
|
||||
```
|
||||
|
||||
### General options and remarks
|
||||
|
||||
#### Value conversion
|
||||
|
||||
The plugin will try to convert the value of the field to the target data type. If the conversion is not possible without precision loss value is converted and warning is logged.
|
||||
|
||||
Conversions are allowed between all supported data types.
|
||||
|
||||
### Examples
|
||||
|
||||
In the following example, we read some registers from a Modbus device and serialize them into a binary protocol.
|
||||
|
||||
```toml
|
||||
# Retrieve data from MODBUS slave devices
|
||||
[[inputs.modbus]]
|
||||
name = "device"
|
||||
slave_id = 1
|
||||
timeout = "1s"
|
||||
|
||||
controller = "tcp://127.0.0.1:5020"
|
||||
|
||||
configuration_type = "register"
|
||||
|
||||
holding_registers = [
|
||||
{ name = "addr_2", byte_order = "AB", data_type="UINT16", scale=1.0, address = [2] },
|
||||
{ name = "addr_3", byte_order = "AB", data_type="UINT16", scale=1.0, address = [3] },
|
||||
{ name = "addr_4_5", byte_order = "ABCD", data_type="UINT32", scale=1.0, address = [4,5] },
|
||||
{ name = "addr_6_7", byte_order = "ABCD", data_type="FLOAT32-IEEE", scale=1.0, address = [6,7] },
|
||||
{ name = "addr_16_20", byte_order = "ABCD", data_type="STRING", address = [16,17,18,19,20] },
|
||||
{ name = "addr_3_sc", byte_order = "AB", data_type="UFIXED", scale=0.1, address = [3] }
|
||||
]
|
||||
|
||||
[[outputs.socket_writer]]
|
||||
address = "tcp://127.0.0.1:54000"
|
||||
metric_batch_size = 1
|
||||
|
||||
data_format = "binary"
|
||||
endianness = "little"
|
||||
entries = [
|
||||
{ read_from = "field", name = "addr_3", data_format="int16" },
|
||||
{ read_from = "field", name = "addr_2", data_format="int16" },
|
||||
{ read_from = "field", name = "addr_4_5", data_format="int32" },
|
||||
{ read_from = "field", name = "addr_6_7", data_format="float32" },
|
||||
{ read_from = "field", name = "addr_16_20", data_format="string", string_terminator = "null", string_length = 11 },
|
||||
{ read_from = "field", name = "addr_3_sc", data_format="float64" },
|
||||
{ read_from = "time", data_format="int32", time_format="unix" },
|
||||
{ read_from = "name", data_format="string", string_terminator = "null", string_length = 20 }
|
||||
]
|
||||
```
|
||||
|
||||
On the receiving side, we expect the following message structure:
|
||||
|
||||
```cpp
|
||||
#pragma pack(push, 1)
|
||||
struct test_struct
|
||||
{
|
||||
short addr_3;
|
||||
short addr_2;
|
||||
int addr_4_5;
|
||||
float addr_6_7;
|
||||
char addr_16_20[11];
|
||||
double addr_3_sc;
|
||||
int time;
|
||||
char metric_name[20];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
```
|
||||
|
||||
Produced message:
|
||||
|
||||
```text
|
||||
69420700296a0900c395d343415f425f435f445f455f006766666666909a407c0082656d6f646275730000000000000000000000000000
|
||||
```
|
||||
|
||||
| addr_3 | addr_2 | addr_4_5 | addr_6_7 | addr_16_20 | addr_3_sc | time | metric_name |
|
||||
|--------|--------|----------|-------------------|------------------------|--------------------|------------|--------------------------------------------|
|
||||
| 0x6942 | 0700 | 296a0900 | c395d343 | 415f425f435f445f455f00 | 6766666666909a40 | 0x7c008265 | 0x6d6f646275730000000000000000000000000000 |
|
||||
| 17001 | 7 | 617001 | 423.1700134277344 | A_B_C_D_E_ | 1700.1000000000001 | 1703018620 | modbus |
|
108
plugins/serializers/binary/binary.go
Normal file
108
plugins/serializers/binary/binary.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package binary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/plugins/serializers"
|
||||
)
|
||||
|
||||
type Serializer struct {
|
||||
Entries []*Entry `toml:"entries"`
|
||||
Endianness string `toml:"endianness"`
|
||||
|
||||
converter binary.ByteOrder
|
||||
}
|
||||
|
||||
func (s *Serializer) Init() error {
|
||||
switch s.Endianness {
|
||||
case "big":
|
||||
s.converter = binary.BigEndian
|
||||
case "little":
|
||||
s.converter = binary.LittleEndian
|
||||
case "", "host":
|
||||
s.Endianness = "host"
|
||||
s.converter = internal.HostEndianness
|
||||
default:
|
||||
return fmt.Errorf("invalid endianness %q", s.Endianness)
|
||||
}
|
||||
|
||||
for i, entry := range s.Entries {
|
||||
if err := entry.fillDefaults(); err != nil {
|
||||
return fmt.Errorf("entry %d check failed: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Serializer) Serialize(metric telegraf.Metric) ([]byte, error) {
|
||||
serialized := make([]byte, 0)
|
||||
|
||||
for _, entry := range s.Entries {
|
||||
switch entry.ReadFrom {
|
||||
case "field":
|
||||
field, found := metric.GetField(entry.Name)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("field %s not found", entry.Name)
|
||||
}
|
||||
|
||||
entryBytes, err := entry.serializeValue(field, s.converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serialized = append(serialized, entryBytes...)
|
||||
case "tag":
|
||||
tag, found := metric.GetTag(entry.Name)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("tag %s not found", entry.Name)
|
||||
}
|
||||
|
||||
entryBytes, err := entry.serializeValue(tag, s.converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serialized = append(serialized, entryBytes...)
|
||||
case "time":
|
||||
entryBytes, err := entry.serializeValue(metric.Time(), s.converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serialized = append(serialized, entryBytes...)
|
||||
case "name":
|
||||
entryBytes, err := entry.serializeValue(metric.Name(), s.converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serialized = append(serialized, entryBytes...)
|
||||
}
|
||||
}
|
||||
|
||||
return serialized, nil
|
||||
}
|
||||
|
||||
func (s *Serializer) SerializeBatch(metrics []telegraf.Metric) ([]byte, error) {
|
||||
serialized := make([]byte, 0)
|
||||
|
||||
for _, metric := range metrics {
|
||||
m, err := s.Serialize(metric)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serialized = append(serialized, m...)
|
||||
}
|
||||
|
||||
return serialized, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
serializers.Add("binary",
|
||||
func() telegraf.Serializer {
|
||||
return &Serializer{}
|
||||
},
|
||||
)
|
||||
}
|
121
plugins/serializers/binary/binary_test.go
Normal file
121
plugins/serializers/binary/binary_test.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package binary
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
)
|
||||
|
||||
func TestMetricSerialization(t *testing.T) {
|
||||
m := metric.New(
|
||||
"modbus",
|
||||
map[string]string{
|
||||
"tag_1": "ABC",
|
||||
"tag_2": "1.63",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"addr_2": 7,
|
||||
"addr_3": 17001,
|
||||
"addr_4_5": 617001,
|
||||
"addr_6_7": 423.1700134277344,
|
||||
"addr_16_20": "A_B_C_D_E_",
|
||||
"addr_3_sc": 1700.1000000000001,
|
||||
},
|
||||
time.Unix(1703018620, 0),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entries []*Entry
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
name: "complex metric serialization",
|
||||
entries: []*Entry{
|
||||
{
|
||||
ReadFrom: "field",
|
||||
Name: "addr_3",
|
||||
DataFormat: "int16",
|
||||
},
|
||||
{
|
||||
ReadFrom: "field",
|
||||
Name: "addr_2",
|
||||
DataFormat: "int16",
|
||||
},
|
||||
{
|
||||
ReadFrom: "field",
|
||||
Name: "addr_4_5",
|
||||
DataFormat: "int32",
|
||||
},
|
||||
{
|
||||
ReadFrom: "field",
|
||||
Name: "addr_6_7",
|
||||
DataFormat: "float32",
|
||||
},
|
||||
{
|
||||
ReadFrom: "field",
|
||||
Name: "addr_16_20",
|
||||
DataFormat: "string",
|
||||
StringTerminator: "null",
|
||||
StringLength: 11,
|
||||
},
|
||||
{
|
||||
ReadFrom: "field",
|
||||
Name: "addr_3_sc",
|
||||
DataFormat: "float64",
|
||||
},
|
||||
{
|
||||
ReadFrom: "time",
|
||||
DataFormat: "int32",
|
||||
TimeFormat: "unix",
|
||||
},
|
||||
{
|
||||
ReadFrom: "name",
|
||||
DataFormat: "string",
|
||||
StringTerminator: "null",
|
||||
StringLength: 20,
|
||||
},
|
||||
{
|
||||
ReadFrom: "tag",
|
||||
Name: "tag_1",
|
||||
DataFormat: "string",
|
||||
StringLength: 4,
|
||||
},
|
||||
{
|
||||
ReadFrom: "tag",
|
||||
Name: "tag_2",
|
||||
DataFormat: "float32",
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"little": "69420700296a0900c395d343415f425f435f445f455f006766666666909a407c" +
|
||||
"0082656d6f64627573000000000000000000000000000041424300d7a3d03f",
|
||||
"big": "4269000700096a2943d395c3415f425f435f445f455f00409a90666666666765" +
|
||||
"82007c6d6f646275730000000000000000000000000000414243003fd0a3d7",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
for endianness, expected := range tc.expected {
|
||||
serializer := &Serializer{
|
||||
Entries: tc.entries,
|
||||
Endianness: endianness,
|
||||
}
|
||||
|
||||
require.NoError(t, serializer.Init())
|
||||
|
||||
serialized, err := serializer.Serialize(m)
|
||||
actual := hex.EncodeToString(serialized)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
133
plugins/serializers/binary/entry.go
Normal file
133
plugins/serializers/binary/entry.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package binary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type converterFunc func(value interface{}, order binary.ByteOrder) ([]byte, error)
|
||||
|
||||
type Entry struct {
|
||||
ReadFrom string `toml:"read_from"` // field, tag, time, name
|
||||
Name string `toml:"name"` // name of entry
|
||||
DataFormat string `toml:"data_format"` // int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, string
|
||||
StringTerminator string `toml:"string_terminator"` // for string metrics: null, 0x00, 00, ....
|
||||
StringLength uint64 `toml:"string_length"` // for string only, target size
|
||||
TimeFormat string `toml:"time_format"` // for time metrics: unix, unix_ms, unix_us, unix_ns
|
||||
|
||||
converter converterFunc
|
||||
termination byte
|
||||
}
|
||||
|
||||
func (e *Entry) fillDefaults() error {
|
||||
// Normalize
|
||||
e.ReadFrom = strings.ToLower(e.ReadFrom)
|
||||
|
||||
// Check input constraints
|
||||
switch e.ReadFrom {
|
||||
case "":
|
||||
e.ReadFrom = "field"
|
||||
fallthrough
|
||||
case "field", "tag":
|
||||
if e.Name == "" {
|
||||
return errors.New("missing name")
|
||||
}
|
||||
case "time":
|
||||
switch e.TimeFormat {
|
||||
case "":
|
||||
e.TimeFormat = "unix"
|
||||
case "unix", "unix_ms", "unix_us", "unix_ns":
|
||||
default:
|
||||
return errors.New("invalid time format")
|
||||
}
|
||||
case "name":
|
||||
if e.DataFormat == "" {
|
||||
e.DataFormat = "string"
|
||||
} else if e.DataFormat != "string" {
|
||||
return errors.New("name data format has to be string")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown assignment %q", e.ReadFrom)
|
||||
}
|
||||
|
||||
// Check data format
|
||||
switch e.DataFormat {
|
||||
case "":
|
||||
return errors.New("missing data format")
|
||||
case "float64":
|
||||
e.converter = convertToFloat64
|
||||
case "float32":
|
||||
e.converter = convertToFloat32
|
||||
case "uint64":
|
||||
e.converter = convertToUint64
|
||||
case "uint32":
|
||||
e.converter = convertToUint32
|
||||
case "uint16":
|
||||
e.converter = convertToUint16
|
||||
case "uint8":
|
||||
e.converter = convertToUint8
|
||||
case "int64":
|
||||
e.converter = convertToInt64
|
||||
case "int32":
|
||||
e.converter = convertToInt32
|
||||
case "int16":
|
||||
e.converter = convertToInt16
|
||||
case "int8":
|
||||
e.converter = convertToInt8
|
||||
case "string":
|
||||
switch e.StringTerminator {
|
||||
case "", "null":
|
||||
e.termination = 0x00
|
||||
default:
|
||||
e.StringTerminator = strings.TrimPrefix(e.StringTerminator, "0x")
|
||||
termination, err := hex.DecodeString(e.StringTerminator)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding terminator failed for %q: %w", e.Name, err)
|
||||
}
|
||||
if len(termination) != 1 {
|
||||
return fmt.Errorf("terminator must be a single byte, got %q", e.StringTerminator)
|
||||
}
|
||||
e.termination = termination[0]
|
||||
}
|
||||
|
||||
if e.StringLength < 1 {
|
||||
return errors.New("string length must be at least 1")
|
||||
}
|
||||
e.converter = e.convertToString
|
||||
default:
|
||||
return fmt.Errorf("invalid data format %q for field %q", e.ReadFrom, e.DataFormat)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Entry) serializeValue(value interface{}, order binary.ByteOrder) ([]byte, error) {
|
||||
// Handle normal fields, tags, etc
|
||||
if e.ReadFrom != "time" {
|
||||
return e.converter(value, order)
|
||||
}
|
||||
|
||||
// We need to serialize the time, make sure we actually do get a time and
|
||||
// convert it to the correct timestamp (with scale) first.
|
||||
t, ok := value.(time.Time)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("time expected but got %T", value)
|
||||
}
|
||||
|
||||
var timestamp int64
|
||||
switch e.TimeFormat {
|
||||
case "unix":
|
||||
timestamp = t.Unix()
|
||||
case "unix_ms":
|
||||
timestamp = t.UnixMilli()
|
||||
case "unix_us":
|
||||
timestamp = t.UnixMicro()
|
||||
case "unix_ns":
|
||||
timestamp = t.UnixNano()
|
||||
}
|
||||
return e.converter(timestamp, order)
|
||||
}
|
305
plugins/serializers/binary/entry_test.go
Normal file
305
plugins/serializers/binary/entry_test.go
Normal file
|
@ -0,0 +1,305 @@
|
|||
package binary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
)
|
||||
|
||||
func TestSerialization(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
entry *Entry
|
||||
input interface{}
|
||||
expected map[binary.ByteOrder][]byte
|
||||
overflow bool
|
||||
}{
|
||||
{
|
||||
name: "positive int serialization",
|
||||
entry: &Entry{Name: "test", DataFormat: "int32"},
|
||||
input: 1,
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x00, 0x00, 0x00, 0x01},
|
||||
binary.LittleEndian: {0x01, 0x00, 0x00, 0x00},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative int serialization",
|
||||
entry: &Entry{Name: "test", DataFormat: "int32"},
|
||||
input: -1,
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0xff, 0xff, 0xff, 0xff},
|
||||
binary.LittleEndian: {0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative int serialization | uint32 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "uint32"},
|
||||
input: -1,
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0xff, 0xff, 0xff, 0xff},
|
||||
binary.LittleEndian: {0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
overflow: true,
|
||||
},
|
||||
{
|
||||
name: "uint to int serialization",
|
||||
entry: &Entry{Name: "test", DataFormat: "uint8"},
|
||||
input: uint(1),
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x01},
|
||||
binary.LittleEndian: {0x01},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string serialization",
|
||||
entry: &Entry{Name: "test", DataFormat: "string", StringLength: 4},
|
||||
input: "test",
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x74, 0x65, 0x73, 0x00},
|
||||
binary.LittleEndian: {0x74, 0x65, 0x73, 0x00},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string serialization with terminator",
|
||||
entry: &Entry{Name: "test", DataFormat: "string", StringLength: 5, StringTerminator: "null"},
|
||||
input: "test",
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x74, 0x65, 0x73, 0x74, 0x00},
|
||||
binary.LittleEndian: {0x74, 0x65, 0x73, 0x74, 0x00},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string serialization with hex terminator",
|
||||
entry: &Entry{Name: "test", DataFormat: "string", StringLength: 5, StringTerminator: "0x01"},
|
||||
input: "test",
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x74, 0x65, 0x73, 0x74, 0x01},
|
||||
binary.LittleEndian: {0x74, 0x65, 0x73, 0x74, 0x01},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "time serialization",
|
||||
entry: &Entry{ReadFrom: "time", DataFormat: "uint64"},
|
||||
input: time.Date(2024, time.January, 6, 19, 44, 10, 0, time.UTC),
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x00, 0x00, 0x00, 0x00, 0x65, 0x99, 0xad, 0x8a},
|
||||
binary.LittleEndian: {0x8a, 0xad, 0x99, 0x65, 0x00, 0x00, 0x00, 0x00},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float32 serialization",
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: float32(3.1415),
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x40, 0x49, 0x0e, 0x56},
|
||||
binary.LittleEndian: {0x56, 0x0e, 0x49, 0x40},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float32 serialization | float64 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "float64"},
|
||||
input: float32(3.1415),
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x40, 0x09, 0x21, 0xCA, 0xC0, 0x00, 0x00, 0x00},
|
||||
binary.LittleEndian: {0x00, 0x00, 0x00, 0xC0, 0xCA, 0x21, 0x09, 0x40},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float64 serialization",
|
||||
entry: &Entry{Name: "test", DataFormat: "float64"},
|
||||
input: 3.1415,
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x40, 0x09, 0x21, 0xCA, 0xC0, 0x83, 0x12, 0x6F},
|
||||
binary.LittleEndian: {0x6F, 0x12, 0x83, 0xC0, 0xCA, 0x21, 0x09, 0x40},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float64 serialization | float32 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: 3.1415,
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x40, 0x49, 0x0e, 0x56},
|
||||
binary.LittleEndian: {0x56, 0x0e, 0x49, 0x40},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float64 serialization | int64 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "int64"},
|
||||
input: 3.1415,
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03},
|
||||
binary.LittleEndian: {0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float64 serialization | uint8 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "uint8"},
|
||||
input: 3.1415,
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x03}, binary.LittleEndian: {0x03},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "uint serialization | float32 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: uint(1),
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x3f, 0x80, 0x00, 0x00},
|
||||
binary.LittleEndian: {0x00, 0x00, 0x80, 0x3f},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "uint serialization | float64 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "float64"},
|
||||
input: uint(1),
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
binary.LittleEndian: {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "int serialization | float32 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: -101,
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0xc2, 0xca, 0x00, 0x00},
|
||||
binary.LittleEndian: {0x00, 0x00, 0xca, 0xc2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string serialization | float32 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: "-101.25",
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0xc2, 0xca, 0x80, 0x00},
|
||||
binary.LittleEndian: {0x00, 0x80, 0xca, 0xc2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string serialization | int32 representation",
|
||||
entry: &Entry{Name: "test", DataFormat: "int32"},
|
||||
input: "1",
|
||||
expected: map[binary.ByteOrder][]byte{
|
||||
binary.BigEndian: {0x00, 0x00, 0x00, 0x01},
|
||||
binary.LittleEndian: {0x01, 0x00, 0x00, 0x00},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.NoError(t, tc.entry.fillDefaults())
|
||||
|
||||
for endianness, expected := range tc.expected {
|
||||
value, err := tc.entry.serializeValue(tc.input, endianness)
|
||||
if tc.overflow {
|
||||
require.ErrorIs(t, err, internal.ErrOutOfRange)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, expected, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoNameSerialization(t *testing.T) {
|
||||
e := &Entry{}
|
||||
require.ErrorContains(t, e.fillDefaults(), "missing name")
|
||||
}
|
||||
|
||||
func BenchmarkSerialization(b *testing.B) {
|
||||
entries := []struct {
|
||||
entry *Entry
|
||||
input interface{}
|
||||
}{
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "int32"},
|
||||
input: 1,
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "int32"},
|
||||
input: -1,
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "uint8"},
|
||||
input: uint(1),
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "string", StringLength: 4},
|
||||
input: "test",
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "string", StringLength: 5, StringTerminator: "null"},
|
||||
input: "test",
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "string", StringLength: 5, StringTerminator: "0x01"},
|
||||
input: "test",
|
||||
},
|
||||
{
|
||||
entry: &Entry{ReadFrom: "time", DataFormat: "uint64"},
|
||||
input: time.Date(2024, time.January, 6, 19, 44, 10, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: float32(3.1415),
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "float64"},
|
||||
input: float32(3.1415),
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "float64"},
|
||||
input: 3.1415,
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: 3.1415,
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "int64"},
|
||||
input: 3.1415,
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "uint8"},
|
||||
input: 3.1415,
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: uint(1),
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "float64"},
|
||||
input: uint(1),
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: -101,
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "float32"},
|
||||
input: "-101.25",
|
||||
},
|
||||
{
|
||||
entry: &Entry{Name: "test", DataFormat: "int32"},
|
||||
input: "1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range entries {
|
||||
require.NoError(b, tc.entry.fillDefaults())
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, tc := range entries {
|
||||
_, err := tc.entry.serializeValue(tc.input, binary.BigEndian)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
||||
}
|
110
plugins/serializers/binary/type_conversions.go
Normal file
110
plugins/serializers/binary/type_conversions.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package binary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
)
|
||||
|
||||
func (e *Entry) convertToString(value interface{}, _ binary.ByteOrder) ([]byte, error) {
|
||||
v, err := internal.ToString(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := []byte(v)
|
||||
|
||||
// If string is longer than target length, truncate it and append terminator.
|
||||
// Thus, there is one less place for the data so that the terminator can be placed.
|
||||
if len(buf) >= int(e.StringLength) {
|
||||
dataLength := int(e.StringLength) - 1
|
||||
return append(buf[:dataLength], e.termination), nil
|
||||
}
|
||||
for i := len(buf); i < int(e.StringLength); i++ {
|
||||
buf = append(buf, e.termination)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func convertToUint64(value interface{}, order binary.ByteOrder) ([]byte, error) {
|
||||
buf := make([]byte, 8)
|
||||
|
||||
v, err := internal.ToUint64(value)
|
||||
order.PutUint64(buf, v)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func convertToUint32(value interface{}, order binary.ByteOrder) ([]byte, error) {
|
||||
buf := make([]byte, 4)
|
||||
|
||||
v, err := internal.ToUint32(value)
|
||||
order.PutUint32(buf, v)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func convertToUint16(value interface{}, order binary.ByteOrder) ([]byte, error) {
|
||||
buf := make([]byte, 2)
|
||||
|
||||
v, err := internal.ToUint16(value)
|
||||
order.PutUint16(buf, v)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func convertToUint8(value interface{}, _ binary.ByteOrder) ([]byte, error) {
|
||||
v, err := internal.ToUint8(value)
|
||||
return []byte{v}, err
|
||||
}
|
||||
|
||||
func convertToInt64(value interface{}, order binary.ByteOrder) ([]byte, error) {
|
||||
buf := make([]byte, 8)
|
||||
|
||||
v, err := internal.ToInt64(value)
|
||||
order.PutUint64(buf, uint64(v))
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func convertToInt32(value interface{}, order binary.ByteOrder) ([]byte, error) {
|
||||
buf := make([]byte, 4)
|
||||
|
||||
v, err := internal.ToInt32(value)
|
||||
order.PutUint32(buf, uint32(v))
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func convertToInt16(value interface{}, order binary.ByteOrder) ([]byte, error) {
|
||||
buf := make([]byte, 2)
|
||||
|
||||
v, err := internal.ToInt16(value)
|
||||
order.PutUint16(buf, uint16(v))
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func convertToInt8(value interface{}, _ binary.ByteOrder) ([]byte, error) {
|
||||
v, err := internal.ToInt8(value)
|
||||
return []byte{uint8(v)}, err
|
||||
}
|
||||
|
||||
func convertToFloat64(value interface{}, order binary.ByteOrder) ([]byte, error) {
|
||||
v, err := internal.ToFloat64(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, 8)
|
||||
x := math.Float64bits(v)
|
||||
order.PutUint64(buf, x)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func convertToFloat32(value interface{}, order binary.ByteOrder) ([]byte, error) {
|
||||
v, err := internal.ToFloat32(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, 4)
|
||||
x := math.Float32bits(v)
|
||||
order.PutUint32(buf, x)
|
||||
return buf, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue