1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-05-02 22:17:10 +08:00
Dušan Borovčanin 412593ae94
NOISSUE - Update dependencies (#1838)
* Update dependencies

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update dependencies

Fix Timescale Reader bug.

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Revert influxdb-reader changes

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update dependencies to latest supported versions

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

---------

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
Co-authored-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2023-07-06 20:44:12 +02:00

304 lines
7.2 KiB
Go

package protocol
import (
"fmt"
"io"
"math"
"sort"
"strconv"
"time"
)
// ErrIsNaN is a field error for when a float field is NaN.
var ErrIsNaN = &FieldError{"is NaN"}
// ErrIsInf is a field error for when a float field is Inf.
var ErrIsInf = &FieldError{"is Inf"}
// Encoder marshals Metrics into influxdb line protocol.
// It is not safe for concurrent use, make a new one!
// The default behavior when encountering a field error is to ignore the field and move on.
// If you wish it to error out on field errors, use Encoder.FailOnFieldErr(true)
type Encoder struct {
w io.Writer
fieldSortOrder FieldSortOrder
fieldTypeSupport FieldTypeSupport
failOnFieldError bool
maxLineBytes int
fieldList []*Field
header []byte
footer []byte
pair []byte
precision time.Duration
}
// SetMaxLineBytes sets a maximum length for a line, Encode will error if the generated line is longer
func (e *Encoder) SetMaxLineBytes(i int) {
e.maxLineBytes = i
}
// SetFieldSortOrder sets a sort order for the data.
// The options are:
// NoSortFields (doesn't sort the fields)
// SortFields (sorts the keys in alphabetical order)
func (e *Encoder) SetFieldSortOrder(s FieldSortOrder) {
e.fieldSortOrder = s
}
// SetFieldTypeSupport sets flags for if the encoder supports certain optional field types such as uint64
func (e *Encoder) SetFieldTypeSupport(s FieldTypeSupport) {
e.fieldTypeSupport = s
}
// FailOnFieldErr whether or not to fail on a field error or just move on.
// The default behavior to move on
func (e *Encoder) FailOnFieldErr(s bool) {
e.failOnFieldError = s
}
// SetPrecision sets time precision for writes
// Default is nanoseconds precision
func (e *Encoder) SetPrecision(p time.Duration) {
e.precision = p
}
// NewEncoder gives us an encoder that marshals to a writer in influxdb line protocol
// as defined by:
// https://docs.influxdata.com/influxdb/v1.5/write_protocols/line_protocol_reference/
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{
w: w,
header: make([]byte, 0, 128),
footer: make([]byte, 0, 128),
pair: make([]byte, 0, 128),
fieldList: make([]*Field, 0, 16),
precision: time.Nanosecond,
}
}
// This is here to significantly reduce allocations, wish that we had constant/immutable keyword that applied to
// more complex objects
var comma = []byte(",")
// Encode marshals a Metric to the io.Writer in the Encoder
func (e *Encoder) Encode(m Metric) (int, error) {
err := e.buildHeader(m)
if err != nil {
return 0, err
}
e.buildFooter(m.Time())
// here we make a copy of the *fields so we can do an in-place sort
e.fieldList = append(e.fieldList[:0], m.FieldList()...)
if e.fieldSortOrder == SortFields {
sort.Slice(e.fieldList, func(i, j int) bool {
return e.fieldList[i].Key < e.fieldList[j].Key
})
}
i := 0
totalWritten := 0
pairsLen := 0
firstField := true
for _, field := range e.fieldList {
err = e.buildFieldPair(field.Key, field.Value)
if err != nil {
if e.failOnFieldError {
return 0, err
}
continue
}
bytesNeeded := len(e.header) + pairsLen + len(e.pair) + len(e.footer)
// Additional length needed for field separator `,`
if !firstField {
bytesNeeded++
}
if e.maxLineBytes > 0 && bytesNeeded > e.maxLineBytes {
// Need at least one field per line
if firstField {
return 0, ErrNeedMoreSpace
}
i, err = e.w.Write(e.footer)
if err != nil {
return 0, err
}
pairsLen = 0
totalWritten += i
bytesNeeded = len(e.header) + len(e.pair) + len(e.footer)
if e.maxLineBytes > 0 && bytesNeeded > e.maxLineBytes {
return 0, ErrNeedMoreSpace
}
i, err = e.w.Write(e.header)
if err != nil {
return 0, err
}
totalWritten += i
i, err = e.w.Write(e.pair)
if err != nil {
return 0, err
}
totalWritten += i
pairsLen += len(e.pair)
firstField = false
continue
}
if firstField {
i, err = e.w.Write(e.header)
if err != nil {
return 0, err
}
totalWritten += i
} else {
i, err = e.w.Write(comma)
if err != nil {
return 0, err
}
totalWritten += i
}
i, err = e.w.Write(e.pair)
if err != nil {
return 0, err
}
totalWritten += i
pairsLen += len(e.pair)
firstField = false
}
if firstField {
return 0, ErrNoFields
}
i, err = e.w.Write(e.footer)
if err != nil {
return 0, err
}
totalWritten += i
return totalWritten, nil
}
func (e *Encoder) buildHeader(m Metric) error {
e.header = e.header[:0]
name := nameEscape(m.Name())
if name == "" {
return ErrInvalidName
}
e.header = append(e.header, name...)
for _, tag := range m.TagList() {
key := escape(tag.Key)
value := escape(tag.Value)
// Some keys and values are not encodeable as line protocol, such as
// those with a trailing '\' or empty strings.
if key == "" || value == "" {
continue
}
e.header = append(e.header, ',')
e.header = append(e.header, key...)
e.header = append(e.header, '=')
e.header = append(e.header, value...)
}
e.header = append(e.header, ' ')
return nil
}
func (e *Encoder) buildFieldVal(value interface{}) error {
switch v := value.(type) {
case uint64:
if e.fieldTypeSupport&UintSupport != 0 {
e.pair = append(strconv.AppendUint(e.pair, v, 10), 'u')
} else if v <= uint64(math.MaxInt64) {
e.pair = append(strconv.AppendInt(e.pair, int64(v), 10), 'i')
} else {
e.pair = append(strconv.AppendInt(e.pair, math.MaxInt64, 10), 'i')
}
case int64:
e.pair = append(strconv.AppendInt(e.pair, v, 10), 'i')
case int:
e.pair = append(strconv.AppendInt(e.pair, int64(v), 10), 'i')
case float64:
if math.IsNaN(v) {
return ErrIsNaN
}
if math.IsInf(v, 0) {
return ErrIsInf
}
e.pair = strconv.AppendFloat(e.pair, v, 'f', -1, 64)
case float32:
v32 := float64(v)
if math.IsNaN(v32) {
return ErrIsNaN
}
if math.IsInf(v32, 0) {
return ErrIsInf
}
e.pair = strconv.AppendFloat(e.pair, v32, 'f', -1, 64)
case string:
e.pair = append(e.pair, '"')
e.pair = append(e.pair, stringFieldEscape(v)...)
e.pair = append(e.pair, '"')
case []byte:
e.pair = append(e.pair, '"')
stringFieldEscapeBytes(&e.pair, v)
e.pair = append(e.pair, '"')
case bool:
e.pair = strconv.AppendBool(e.pair, v)
default:
return &FieldError{fmt.Sprintf("invalid value type: %T", v)}
}
return nil
}
func (e *Encoder) buildFieldPair(key string, value interface{}) error {
e.pair = e.pair[:0]
key = escape(key)
// Some keys are not encodeable as line protocol, such as those with a
// trailing '\' or empty strings.
if key == "" || key[:len(key)-1] == "\\" {
return &FieldError{"invalid field key"}
}
e.pair = append(e.pair, key...)
e.pair = append(e.pair, '=')
return e.buildFieldVal(value)
}
func (e *Encoder) buildFooter(t time.Time) {
e.footer = e.footer[:0]
if !t.IsZero() {
e.footer = append(e.footer, ' ')
switch e.precision {
case time.Microsecond:
e.footer = strconv.AppendInt(e.footer, t.UnixNano()/1000, 10)
case time.Millisecond:
e.footer = strconv.AppendInt(e.footer, t.UnixNano()/1000000, 10)
case time.Second:
e.footer = strconv.AppendInt(e.footer, t.Unix(), 10)
default:
e.footer = strconv.AppendInt(e.footer, t.UnixNano(), 10)
}
}
e.footer = append(e.footer, '\n')
}