1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,21 @@
# Wavefront Parser Plugin
Wavefront Data Format is metrics are parsed directly into Telegraf metrics.
For more information about the Wavefront Data Format see
[here](https://docs.wavefront.com/wavefront_data_format.html).
## Configuration
```toml
[[inputs.file]]
files = ["example"]
## 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 = "wavefront"
```
There are no additional configuration options for Wavefront Data Format
line-protocol.

View file

@ -0,0 +1,225 @@
package wavefront
import (
"errors"
"fmt"
"io"
"strconv"
"time"
)
var (
errInvalidTimestamp = errors.New("invalid timestamp")
)
type elementParser interface {
parse(p *PointParser, pt *Point) error
}
type nameParser struct{}
type valueParser struct{}
type timestampParser struct {
optional bool
}
type whiteSpaceParser struct {
nextOptional bool
}
type tagParser struct{}
type loopedParser struct {
wrappedParser elementParser
wsParser *whiteSpaceParser
}
func (*nameParser) parse(p *PointParser, pt *Point) error {
// Valid characters are: a-z, A-Z, 0-9, hyphen ("-"), underscore ("_"), dot (".").
// Forward slash ("/") and comma (",") are allowed if metricName is enclosed in double quotes.
// Delta (U+2206) is allowed as the first character of the
// metricName
name, err := parseLiteral(p)
if err != nil {
return err
}
pt.Name = name
return nil
}
func (*valueParser) parse(p *PointParser, pt *Point) error {
tok, lit := p.scan()
if tok == EOF {
return fmt.Errorf("found %q, expected number", lit)
}
p.writeBuf.Reset()
if tok == MinusSign {
p.writeBuf.WriteString(lit)
tok, lit = p.scan()
}
for tok != EOF && (tok == Letter || tok == Number || tok == Dot || tok == MinusSign) {
p.writeBuf.WriteString(lit)
tok, lit = p.scan()
}
p.unscan()
pt.Value = p.writeBuf.String()
if _, err := strconv.ParseFloat(pt.Value, 64); err != nil {
return fmt.Errorf("invalid metric value %s", pt.Value)
}
return nil
}
func (ep *timestampParser) parse(p *PointParser, pt *Point) error {
tok, lit := p.scan()
if tok == EOF {
if ep.optional {
p.unscanTokens(2)
return setTimestamp(pt, 0, 1)
}
return fmt.Errorf("found %q, expected number", lit)
}
if tok != Number {
if ep.optional {
p.unscanTokens(2)
return setTimestamp(pt, 0, 1)
}
return errInvalidTimestamp
}
p.writeBuf.Reset()
for tok == Number {
p.writeBuf.WriteString(lit)
tok, lit = p.scan()
}
p.unscan()
tsStr := p.writeBuf.String()
ts, err := strconv.ParseInt(tsStr, 10, 64)
if err != nil {
return err
}
return setTimestamp(pt, ts, len(tsStr))
}
func setTimestamp(pt *Point, ts int64, numDigits int) error {
if numDigits == 19 {
// nanoseconds
ts = ts / 1e9
} else if numDigits == 16 {
// microseconds
ts = ts / 1e6
} else if numDigits == 13 {
// milliseconds
ts = ts / 1e3
} else if numDigits != 10 {
// must be in seconds, return error if not 0
if ts != 0 {
return errInvalidTimestamp
}
ts = getCurrentTime()
}
pt.Timestamp = ts
return nil
}
func (ep *loopedParser) parse(p *PointParser, pt *Point) error {
for {
err := ep.wrappedParser.parse(p, pt)
if err != nil {
return err
}
err = ep.wsParser.parse(p, pt)
if errors.Is(err, io.EOF) {
break
}
}
return nil
}
func (*tagParser) parse(p *PointParser, pt *Point) error {
k, err := parseLiteral(p)
if err != nil {
if k == "" {
return nil
}
return err
}
next, lit := p.scan()
if next != Equals {
return fmt.Errorf("found %q, expected equals", lit)
}
v, err := parseLiteral(p)
if err != nil {
return err
}
if len(pt.Tags) == 0 {
pt.Tags = make(map[string]string)
}
pt.Tags[k] = v
return nil
}
func (ep *whiteSpaceParser) parse(p *PointParser, _ *Point) error {
tok := Ws
for tok == Ws {
tok, _ = p.scan()
}
if tok == EOF {
if !ep.nextOptional {
return io.EOF
}
return nil
}
p.unscan()
return nil
}
func parseQuotedLiteral(p *PointParser) (string, error) {
p.writeBuf.Reset()
escaped := false
tok, lit := p.scan()
for tok != EOF && (tok != Quotes || (tok == Quotes && escaped)) {
// let everything through
escaped = tok == Backslash
p.writeBuf.WriteString(lit)
tok, lit = p.scan()
}
if tok == EOF {
return "", fmt.Errorf("found %q, expected quotes", lit)
}
return p.writeBuf.String(), nil
}
func parseLiteral(p *PointParser) (string, error) {
tok, lit := p.scan()
if tok == EOF {
return "", fmt.Errorf("found %q, expected literal", lit)
}
if tok == Quotes {
return parseQuotedLiteral(p)
}
p.writeBuf.Reset()
for tok != EOF && tok > literalBeg && tok < literalEnd {
p.writeBuf.WriteString(lit)
tok, lit = p.scan()
if tok == Delta {
return "", errors.New("found delta inside metric name")
}
}
if tok == Quotes {
return "", errors.New("found quote inside unquoted literal")
}
p.unscan()
return p.writeBuf.String(), nil
}
func getCurrentTime() int64 {
return time.Now().UnixNano() / 1e9
}

View file

@ -0,0 +1,223 @@
package wavefront
import (
"bufio"
"bytes"
"io"
"strconv"
"sync"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/plugins/parsers"
)
const MaxBufferSize = 2
type Point struct {
Name string
Value string
Timestamp int64
Source string
Tags map[string]string
}
type Parser struct {
parsers *sync.Pool
DefaultTags map[string]string `toml:"-"`
Log telegraf.Logger `toml:"-"`
}
// PointParser is a thread-unsafe parser and must be kept in a pool.
type PointParser struct {
s *PointScanner
buf struct {
tok []Token // last read n tokens
lit []string // last read n literals
n int // unscanned buffer size (max=2)
}
scanBuf bytes.Buffer // buffer reused for scanning tokens
writeBuf bytes.Buffer // buffer reused for parsing elements
Elements []elementParser
parent *Parser
}
// NewWavefrontElements returns a slice of elementParser's for the Graphite format
func NewWavefrontElements() []elementParser {
var elements []elementParser
wsParser := whiteSpaceParser{}
wsParserNextOpt := whiteSpaceParser{nextOptional: true}
repeatParser := loopedParser{wrappedParser: &tagParser{}, wsParser: &wsParser}
elements = append(elements, &nameParser{}, &wsParser, &valueParser{}, &wsParserNextOpt,
&timestampParser{optional: true}, &wsParserNextOpt, &repeatParser)
return elements
}
func (p *Parser) Init() error {
p.parsers = &sync.Pool{
New: func() interface{} {
elements := NewWavefrontElements()
return &PointParser{Elements: elements, parent: p}
},
}
return nil
}
func (p *Parser) ParseLine(line string) (telegraf.Metric, error) {
buf := []byte(line)
metrics, err := p.Parse(buf)
if err != nil {
return nil, err
}
if len(metrics) > 0 {
return metrics[0], nil
}
return nil, nil
}
func (p *Parser) Parse(buf []byte) ([]telegraf.Metric, error) {
pp := p.parsers.Get().(*PointParser)
defer p.parsers.Put(pp)
return pp.Parse(buf)
}
func (p *PointParser) Parse(buf []byte) ([]telegraf.Metric, error) {
// parse even if the buffer begins with a newline
buf = bytes.TrimPrefix(buf, []byte("\n"))
// add newline to end if not exists:
if len(buf) > 0 && !bytes.HasSuffix(buf, []byte("\n")) {
buf = append(buf, []byte("\n")...)
}
points := make([]Point, 0)
buffer := bytes.NewBuffer(buf)
reader := bufio.NewReader(buffer)
for {
// Read up to the next newline.
buf, err := reader.ReadBytes('\n')
if err == io.EOF {
break
}
p.reset(buf)
point := Point{}
for _, element := range p.Elements {
err := element.parse(p, &point)
if err != nil {
return nil, err
}
}
points = append(points, point)
}
metrics, err := p.convertPointToTelegrafMetric(points)
if err != nil {
return nil, err
}
return metrics, nil
}
func (p *Parser) SetDefaultTags(tags map[string]string) {
p.DefaultTags = tags
}
func (p *PointParser) convertPointToTelegrafMetric(points []Point) ([]telegraf.Metric, error) {
metrics := make([]telegraf.Metric, 0)
for _, point := range points {
tags := make(map[string]string)
for k, v := range point.Tags {
tags[k] = v
}
// apply default tags after parsed tags
for k, v := range p.parent.DefaultTags {
tags[k] = v
}
// single field for value
fields := make(map[string]interface{})
v, err := strconv.ParseFloat(point.Value, 64)
if err != nil {
return nil, err
}
fields["value"] = v
m := metric.New(point.Name, tags, fields, time.Unix(point.Timestamp, 0))
metrics = append(metrics, m)
}
return metrics, nil
}
// scan returns the next token from the underlying scanner.
// If a token has been unscanned then read that from the internal buffer instead.
func (p *PointParser) scan() (Token, string) {
// If we have a token on the buffer, then return it.
if p.buf.n != 0 {
idx := p.buf.n % MaxBufferSize
tok, lit := p.buf.tok[idx], p.buf.lit[idx]
p.buf.n--
return tok, lit
}
// Otherwise read the next token from the scanner.
tok, lit := p.s.Scan()
// Save it to the buffer in case we unscan later.
p.buffer(tok, lit)
return tok, lit
}
func (p *PointParser) buffer(tok Token, lit string) {
// create the buffer if it is empty
if len(p.buf.tok) == 0 {
p.buf.tok = make([]Token, MaxBufferSize)
p.buf.lit = make([]string, MaxBufferSize)
}
// for now assume a simple circular buffer of length two
p.buf.tok[0], p.buf.lit[0] = p.buf.tok[1], p.buf.lit[1]
p.buf.tok[1], p.buf.lit[1] = tok, lit
}
// unscan pushes the previously read token back onto the buffer.
func (p *PointParser) unscan() {
p.unscanTokens(1)
}
func (p *PointParser) unscanTokens(n int) {
if n > MaxBufferSize {
// just log for now
p.parent.Log.Infof("Cannot unscan more than %d tokens", MaxBufferSize)
}
p.buf.n += n
}
func (p *PointParser) reset(buf []byte) {
// reset the scan buffer and write new byte
p.scanBuf.Reset()
p.scanBuf.Write(buf)
if p.s == nil {
p.s = NewScanner(&p.scanBuf)
} else {
// reset p.s.r passing in the buffer as the reader
p.s.r.Reset(&p.scanBuf)
}
p.buf.n = 0
}
func init() {
parsers.Add("wavefront",
func(string) telegraf.Parser {
return &Parser{}
})
}

View file

@ -0,0 +1,342 @@
package wavefront
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)
func TestParse(t *testing.T) {
parser := &Parser{}
require.NoError(t, parser.Init())
parsedMetrics, err := parser.Parse([]byte("test.metric 1"))
require.NoError(t, err)
testMetric := metric.New("test.metric", map[string]string{}, map[string]interface{}{"value": 1.}, time.Unix(0, 0))
require.Equal(t, parsedMetrics[0].Name(), testMetric.Name())
require.Equal(t, parsedMetrics[0].Fields(), testMetric.Fields())
parsedMetrics, err = parser.Parse([]byte("\u2206test.delta 1 1530939936"))
require.NoError(t, err)
testMetric = metric.New("\u2206test.delta", map[string]string{},
map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("\u0394test.delta 1 1530939936"))
require.NoError(t, err)
testMetric = metric.New("\u0394test.delta", map[string]string{},
map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("\u0394test.delta 1.234 1530939936 source=\"mysource\" tag2=value2"))
require.NoError(t, err)
testMetric = metric.New(
"\u0394test.delta",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": 1.234},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("test.metric 1 1530939936"))
require.NoError(t, err)
testMetric = metric.New("test.metric", map[string]string{}, map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("test.metric 1 1530939936 source=mysource"))
require.NoError(t, err)
testMetric = metric.New("test.metric", map[string]string{"source": "mysource"}, map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("\"test.metric\" 1.1234 1530939936 source=\"mysource\""))
require.NoError(t, err)
testMetric = metric.New("test.metric", map[string]string{"source": "mysource"}, map[string]interface{}{"value": 1.1234}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("\"test.metric\" 1.1234 1530939936 \"source\"=\"mysource\" tag2=value2"))
require.NoError(t, err)
testMetric = metric.New(
"test.metric",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": 1.1234},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("\"test.metric\" -1.1234 1530939936 \"source\"=\"mysource\" tag2=value2"))
require.NoError(t, err)
testMetric = metric.New(
"test.metric",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": -1.1234},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("\"test.metric\" 1.1234e04 1530939936 \"source\"=\"mysource\" tag2=value2"))
require.NoError(t, err)
testMetric = metric.New(
"test.metric",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": 1.1234e04},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("\"test.metric\" 1.1234e-04 1530939936 \"source\"=\"mysource\" tag2=value2"))
require.NoError(t, err)
testMetric = metric.New(
"test.metric",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": 1.1234e-04},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("test.metric 1.1234 1530939936 source=\"mysource\" tag2=value2 "))
require.NoError(t, err)
testMetric = metric.New(
"test.metric",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": 1.1234},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetrics[0], testMetric)
}
func TestParseLine(t *testing.T) {
parser := &Parser{}
require.NoError(t, parser.Init())
parsedMetric, err := parser.ParseLine("test.metric 1")
require.NoError(t, err)
testMetric := metric.New("test.metric", map[string]string{}, map[string]interface{}{"value": 1.}, time.Unix(0, 0))
require.Equal(t, parsedMetric.Name(), testMetric.Name())
require.Equal(t, parsedMetric.Fields(), testMetric.Fields())
parsedMetric, err = parser.ParseLine("test.metric 1 1530939936")
require.NoError(t, err)
testMetric = metric.New("test.metric", map[string]string{}, map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetric, testMetric)
parsedMetric, err = parser.ParseLine("test.metric 1 1530939936 source=mysource")
require.NoError(t, err)
testMetric = metric.New("test.metric", map[string]string{"source": "mysource"}, map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetric, testMetric)
parsedMetric, err = parser.ParseLine("\"test.metric\" 1.1234 1530939936 source=\"mysource\"")
require.NoError(t, err)
testMetric = metric.New("test.metric", map[string]string{"source": "mysource"}, map[string]interface{}{"value": 1.1234}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetric, testMetric)
parsedMetric, err = parser.ParseLine("\"test.metric\" 1.1234 1530939936 \"source\"=\"mysource\" tag2=value2")
require.NoError(t, err)
testMetric = metric.New(
"test.metric",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": 1.1234},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetric, testMetric)
parsedMetric, err = parser.ParseLine("test.metric 1.1234 1530939936 source=\"mysource\" tag2=value2 ")
require.NoError(t, err)
testMetric = metric.New(
"test.metric",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": 1.1234},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetric, testMetric)
}
func TestParseMultiple(t *testing.T) {
parser := &Parser{}
require.NoError(t, parser.Init())
parsedMetrics, err := parser.Parse([]byte("test.metric 1\ntest.metric2 2 1530939936"))
require.NoError(t, err)
testMetric1 := metric.New("test.metric", map[string]string{}, map[string]interface{}{"value": 1.}, time.Unix(0, 0))
testMetric2 := metric.New("test.metric2", map[string]string{}, map[string]interface{}{"value": 2.}, time.Unix(1530939936, 0))
testMetrics := []telegraf.Metric{testMetric1, testMetric2}
require.Equal(t, parsedMetrics[0].Name(), testMetrics[0].Name())
require.Equal(t, parsedMetrics[0].Fields(), testMetrics[0].Fields())
require.EqualValues(t, parsedMetrics[1], testMetrics[1])
parsedMetrics, err = parser.Parse([]byte("test.metric 1 1530939936 source=mysource\n\"test.metric\" 1.1234 1530939936 source=\"mysource\""))
require.NoError(t, err)
testMetric1 = metric.New("test.metric", map[string]string{"source": "mysource"}, map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
testMetric2 = metric.New("test.metric", map[string]string{"source": "mysource"}, map[string]interface{}{"value": 1.1234}, time.Unix(1530939936, 0))
testMetrics = []telegraf.Metric{testMetric1, testMetric2}
require.EqualValues(t, parsedMetrics, testMetrics)
parsedMetrics, err = parser.Parse(
[]byte(
"\"test.metric\" 1.1234 1530939936 \"source\"=\"mysource\" tag2=value2\n" +
"test.metric 1.1234 1530939936 source=\"mysource\" tag2=value2 ",
),
)
require.NoError(t, err)
testMetric1 = metric.New(
"test.metric",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": 1.1234},
time.Unix(1530939936, 0),
)
testMetric2 = metric.New(
"test.metric",
map[string]string{"source": "mysource", "tag2": "value2"},
map[string]interface{}{"value": 1.1234},
time.Unix(1530939936, 0),
)
testMetrics = []telegraf.Metric{testMetric1, testMetric2}
require.EqualValues(t, parsedMetrics, testMetrics)
parsedMetrics, err = parser.Parse(
[]byte("test.metric 1 1530939936 source=mysource\n\"test.metric\" 1.1234 1530939936 source=\"mysource\"\ntest.metric3 333 1530939936 tagit=valueit"),
)
require.NoError(t, err)
testMetric1 = metric.New("test.metric", map[string]string{"source": "mysource"}, map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
testMetric2 = metric.New("test.metric", map[string]string{"source": "mysource"}, map[string]interface{}{"value": 1.1234}, time.Unix(1530939936, 0))
testMetric3 := metric.New("test.metric3", map[string]string{"tagit": "valueit"}, map[string]interface{}{"value": 333.}, time.Unix(1530939936, 0))
testMetrics = []telegraf.Metric{testMetric1, testMetric2, testMetric3}
require.EqualValues(t, parsedMetrics, testMetrics)
}
func TestParseSpecial(t *testing.T) {
parser := &Parser{}
require.NoError(t, parser.Init())
parsedMetric, err := parser.ParseLine("\"test.metric\" 1 1530939936")
require.NoError(t, err)
testMetric := metric.New("test.metric", map[string]string{}, map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetric, testMetric)
parsedMetric, err = parser.ParseLine("test.metric 1 1530939936 tag1=\"val\\\"ue1\"")
require.NoError(t, err)
testMetric = metric.New("test.metric", map[string]string{"tag1": "val\\\"ue1"}, map[string]interface{}{"value": 1.}, time.Unix(1530939936, 0))
require.EqualValues(t, parsedMetric, testMetric)
}
func TestParseInvalid(t *testing.T) {
parser := &Parser{}
require.NoError(t, parser.Init())
_, err := parser.Parse([]byte("test.metric"))
require.Error(t, err)
_, err = parser.Parse([]byte("test.metric string"))
require.Error(t, err)
_, err = parser.Parse([]byte("test.metric 1 string"))
require.Error(t, err)
_, err = parser.Parse([]byte("test.\u2206delta 1"))
require.Error(t, err)
_, err = parser.Parse([]byte("test.metric 1 1530939936 tag_no_pair"))
require.Error(t, err)
_, err = parser.Parse([]byte("test.metric 1 1530939936 tag_broken_value=\""))
require.Error(t, err)
_, err = parser.Parse([]byte("\"test.metric 1 1530939936"))
require.Error(t, err)
_, err = parser.Parse([]byte("test.metric 1 1530939936 tag1=val\\\"ue1"))
require.Error(t, err)
_, err = parser.Parse([]byte("\"test.metric\" -1.12-34 1530939936 \"source\"=\"mysource\" tag2=value2"))
require.Error(t, err)
}
func TestParseDefaultTags(t *testing.T) {
parser := &Parser{}
require.NoError(t, parser.Init())
parser.SetDefaultTags(map[string]string{"myDefault": "value1", "another": "test2"})
parsedMetrics, err := parser.Parse([]byte("test.metric 1 1530939936"))
require.NoError(t, err)
testMetric := metric.New(
"test.metric",
map[string]string{"myDefault": "value1", "another": "test2"},
map[string]interface{}{"value": 1.},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("test.metric 1 1530939936 source=mysource"))
require.NoError(t, err)
testMetric = metric.New(
"test.metric",
map[string]string{"myDefault": "value1", "another": "test2", "source": "mysource"},
map[string]interface{}{"value": 1.},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetrics[0], testMetric)
parsedMetrics, err = parser.Parse([]byte("\"test.metric\" 1.1234 1530939936 another=\"test3\""))
require.NoError(t, err)
testMetric = metric.New(
"test.metric",
map[string]string{"myDefault": "value1", "another": "test2"},
map[string]interface{}{"value": 1.1234},
time.Unix(1530939936, 0),
)
require.EqualValues(t, parsedMetrics[0], testMetric)
}
const benchmarkData = `benchmark 5 1653643420 source="myhost" tags_platform="python" tags_sdkver="3.11.5"
benchmark 4 1653643420 source="myhost" tags_platform="python" tags_sdkver="3.11.4"
`
func TestBenchmarkData(t *testing.T) {
plugin := &Parser{}
require.NoError(t, plugin.Init())
expected := []telegraf.Metric{
metric.New(
"benchmark",
map[string]string{
"source": "myhost",
"tags_platform": "python",
"tags_sdkver": "3.11.5",
},
map[string]interface{}{
"value": 5.0,
},
time.Unix(1653643420, 0),
),
metric.New(
"benchmark",
map[string]string{
"source": "myhost",
"tags_platform": "python",
"tags_sdkver": "3.11.4",
},
map[string]interface{}{
"value": 4.0,
},
time.Unix(1653643420, 0),
),
}
actual, err := plugin.Parse([]byte(benchmarkData))
require.NoError(t, err)
testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics())
}
func BenchmarkParsing(b *testing.B) {
plugin := &Parser{}
require.NoError(b, plugin.Init())
for n := 0; n < b.N; n++ {
//nolint:errcheck // Benchmarking so skip the error check to avoid the unnecessary operations
plugin.Parse([]byte(benchmarkData))
}
}

View file

@ -0,0 +1,65 @@
package wavefront
import (
"bufio"
"io"
)
// Lexical Point Scanner
type PointScanner struct {
r *bufio.Reader
}
func NewScanner(r io.Reader) *PointScanner {
return &PointScanner{r: bufio.NewReader(r)}
}
// read reads the next rune from the buffered reader.
// Returns rune(0) if an error occurs (or io.EOF is returned).
func (s *PointScanner) read() rune {
ch, _, err := s.r.ReadRune()
if err != nil {
return eof
}
return ch
}
// Scan returns the next token and literal value.
func (s *PointScanner) Scan() (Token, string) {
// Read the next rune
ch := s.read()
if isWhitespace(ch) {
return Ws, string(ch)
} else if isLetter(ch) {
return Letter, string(ch)
} else if isNumber(ch) {
return Number, string(ch)
} else if isDelta(ch) {
return Delta, string(ch)
}
// Otherwise read the individual character.
switch ch {
case eof:
return EOF, ""
case '\n':
return Newline, string(ch)
case '.':
return Dot, string(ch)
case '-':
return MinusSign, string(ch)
case '_':
return Underscore, string(ch)
case '/':
return Slash, string(ch)
case '\\':
return Backslash, string(ch)
case ',':
return Comma, string(ch)
case '"':
return Quotes, string(ch)
case '=':
return Equals, string(ch)
}
return Illegal, string(ch)
}

View file

@ -0,0 +1,46 @@
package wavefront
type Token int
const (
// Special tokens
Illegal Token = iota
EOF
Ws
// Literals
literalBeg
Letter // metric name, source/point tags
Number
MinusSign
Underscore
Dot
Slash
Backslash
Comma
Delta
literalEnd
// Misc characters
Quotes
Equals
Newline
)
func isWhitespace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n'
}
func isLetter(ch rune) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
}
func isNumber(ch rune) bool {
return ch >= '0' && ch <= '9'
}
func isDelta(ch rune) bool {
return ch == '\u2206' || ch == '\u0394'
}
var eof = rune(0)