225 lines
4.3 KiB
Go
225 lines
4.3 KiB
Go
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
|
|
}
|