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
21
plugins/parsers/wavefront/README.md
Normal file
21
plugins/parsers/wavefront/README.md
Normal 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.
|
225
plugins/parsers/wavefront/element.go
Normal file
225
plugins/parsers/wavefront/element.go
Normal 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
|
||||
}
|
223
plugins/parsers/wavefront/parser.go
Normal file
223
plugins/parsers/wavefront/parser.go
Normal 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,
|
||||
×tampParser{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{}
|
||||
})
|
||||
}
|
342
plugins/parsers/wavefront/parser_test.go
Normal file
342
plugins/parsers/wavefront/parser_test.go
Normal 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))
|
||||
}
|
||||
}
|
65
plugins/parsers/wavefront/scanner.go
Normal file
65
plugins/parsers/wavefront/scanner.go
Normal 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)
|
||||
}
|
46
plugins/parsers/wavefront/token.go
Normal file
46
plugins/parsers/wavefront/token.go
Normal 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)
|
Loading…
Add table
Add a link
Reference in a new issue