Repository: atlassian/gostatsd
Pull request #530 (2022-07-05 02:12:10)

Lbest/container bugfix
This PR addresses the issue [https://github.com/atlassian/gostatsd/issues/524]. A change to dogstatsd client to add a container attribute causes the last tag to have |c:{container_id} appended when parsed by gostatsd parser. 

Currently tags are assumed to be the last attribute for both events and metrics. To address the issue, the lexer is being updated to:

- accept optional attributes in any order including the tags attribute
- ignore any attribute that is unrecognised, including container

This change has been applied consistently to parsing of both the events and metrics.

Implementation notes:
- the initial implementation of this fix attempted to use lexAssert/lexUntil functions in the attribute parsing for unknown attributes. However, this seemed to increase the benchmark time to process per metric due to the return of anonymous functions.
- to maintain performance, lexUntil function was modified and renamed to seekUntil.
- to improve readability seekUntil was used to replace existing code for parsing sample rate
- a new function seekDelimited was created to replace the lextTags. There was some performance improvement due to inlining the lexTags implementation.


tiedotguy: lgtm, but because of it's criticality, we should do an rc build, and get it in front of some services.  You can push a tag like `35.1.0-rc1` which will trigger the build (+docker build), then I believe we can consume that internally somehow.
aelse: lgtm but @tiedotguy can gatekeep this release

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ee1fce..77312d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+35.0.1
+------
+- Update to Alpine 3.15.5
+
 35.0.0
 ------
 - Adds a statsdSource tag to the New Relic backend, see [BACKENDS.md](BACKENDS.md) for details.
diff --git a/build/Dockerfile b/build/Dockerfile
index 5841208..7247bb2 100644
--- a/build/Dockerfile
+++ b/build/Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.15.4
+FROM alpine:3.15.5
 
 RUN apk --no-cache add \
     ca-certificates
diff --git a/internal/lexer/lexer.go b/internal/lexer/lexer.go
index be575cc..315c656 100644
--- a/internal/lexer/lexer.go
+++ b/internal/lexer/lexer.go
@@ -32,16 +32,15 @@ type Lexer struct {
 const eof byte = 0
 
 var (
-	errMissingKeySep         = errors.New("missing key separator")
-	errEmptyKey              = errors.New("key zero len")
-	errMissingValueSep       = errors.New("missing value separator")
-	errInvalidType           = errors.New("invalid type")
-	errInvalidFormat         = errors.New("invalid format")
-	errInvalidSamplingOrTags = errors.New("invalid sampling or tags")
-	errInvalidAttributes     = errors.New("invalid event attributes")
-	errOverflow              = errors.New("overflow")
-	errNotEnoughData         = errors.New("not enough data")
-	errNaN                   = errors.New("invalid value NaN")
+	errMissingKeySep     = errors.New("missing key separator")
+	errEmptyKey          = errors.New("key zero len")
+	errMissingValueSep   = errors.New("missing value separator")
+	errInvalidType       = errors.New("invalid type")
+	errInvalidFormat     = errors.New("invalid format")
+	errInvalidAttributes = errors.New("invalid event attributes")
+	errOverflow          = errors.New("overflow")
+	errNotEnoughData     = errors.New("not enough data")
+	errNaN               = errors.New("invalid value NaN")
 )
 
 var escapedNewline = []byte("\\n")
@@ -80,6 +79,12 @@ func (l *Lexer) reset() {
 	l.err = nil
 }
 
+func (l *Lexer) appendTag(data []byte) {
+	if len(data) > 0 {
+		l.tags = append(l.tags, string(data))
+	}
+}
+
 func (l *Lexer) Run(input []byte, namespace string) (*gostatsd.Metric, *gostatsd.Event, error) {
 	l.reset()
 	l.input = input
@@ -192,6 +197,7 @@ func lexEventBody(l *Lexer) stateFn {
 	return lexEventAttributes
 }
 
+// lexEventAttributes lex through any event attributes separated by '|'
 func lexEventAttributes(l *Lexer) stateFn {
 	switch b := l.next(); b {
 	case '|':
@@ -203,8 +209,9 @@ func lexEventAttributes(l *Lexer) stateFn {
 	return nil
 }
 
+// lexEventAttribute
 func lexEventAttribute(l *Lexer) stateFn {
-	// d:date_happened|h:hostname|p:priority|t:alert_type|#tag1,tag2
+	// d:date_happened|h:hostname|p:priority|t:alert_type|#tag1,tag2|c:container
 	switch b := l.next(); b {
 	case 'd':
 		return lexAssert(':', lexUint(func(l *Lexer, value uint64) stateFn {
@@ -216,17 +223,20 @@ func lexEventAttribute(l *Lexer) stateFn {
 			return lexEventAttributes
 		}))
 	case 'h':
-		return lexAssert(':', lexUntil('|', func(l *Lexer, data []byte) stateFn {
+		return lexAssert(':', func(lexer *Lexer) stateFn {
+			data := seekUntil(l, '|')
 			l.e.Source = gostatsd.Source(data)
 			return lexEventAttributes
-		}))
+		})
 	case 'k':
-		return lexAssert(':', lexUntil('|', func(l *Lexer, data []byte) stateFn {
+		return lexAssert(':', func(lexer *Lexer) stateFn {
+			data := seekUntil(l, '|')
 			l.e.AggregationKey = string(data)
 			return lexEventAttributes
-		}))
+		})
 	case 'p':
-		return lexAssert(':', lexUntil('|', func(l *Lexer, data []byte) stateFn {
+		return lexAssert(':', func(lexer *Lexer) stateFn {
+			data := seekUntil(l, '|')
 			if bytes.Equal(data, priorityLow) {
 				l.e.Priority = gostatsd.PriLow
 			} else if bytes.Equal(data, priorityNormal) {
@@ -236,14 +246,16 @@ func lexEventAttribute(l *Lexer) stateFn {
 				return nil
 			}
 			return lexEventAttributes
-		}))
+		})
 	case 's':
-		return lexAssert(':', lexUntil('|', func(l *Lexer, data []byte) stateFn {
+		return lexAssert(':', func(lexer *Lexer) stateFn {
+			data := seekUntil(l, '|')
 			l.e.SourceTypeName = string(data)
 			return lexEventAttributes
-		}))
+		})
 	case 't':
-		return lexAssert(':', lexUntil('|', func(l *Lexer, data []byte) stateFn {
+		return lexAssert(':', func(lexer *Lexer) stateFn {
+			data := seekUntil(l, '|')
 			if bytes.Equal(data, alertError) {
 				l.e.AlertType = gostatsd.AlertError
 			} else if bytes.Equal(data, alertWarning) {
@@ -257,14 +269,21 @@ func lexEventAttribute(l *Lexer) stateFn {
 				return nil
 			}
 			return lexEventAttributes
-		}))
+		})
 	case '#':
-		return lexTags
-	case eof:
+		for {
+			data, complete := seekDelimited(l, '|', ',')
+			l.appendTag(data)
+			if complete {
+				break
+			}
+		}
+		return lexEventAttributes
 	default:
-		l.err = errInvalidAttributes
+		// error no longer raised allow new fields to be sent but ignored
+		_ = seekUntil(l, '|')
+		return lexEventAttributes
 	}
-	return nil
 }
 
 func lexUint32(target *uint32, next stateFn) stateFn {
@@ -320,19 +339,35 @@ func lexAssert(nextByte byte, next stateFn) stateFn {
 	}
 }
 
-// lexUntil invokes handler with all bytes up to the stop byte or an eof.
-// The stop byte is not consumed.
-func lexUntil(stop byte, handler func(*Lexer, []byte) stateFn) stateFn {
-	return func(l *Lexer) stateFn {
-		start := l.pos
-		p := bytes.IndexByte(l.input[l.pos:], stop)
-		switch p {
-		case -1:
-			l.pos = l.len
-		default:
-			l.pos += uint32(p)
+// seekUntil Seeks the next stop or eof byte and then return the byte slice between
+// the start position and the end byte.
+func seekUntil(l *Lexer, stop byte) []byte {
+	l.start = l.pos
+	p := bytes.IndexByte(l.input[l.pos:], stop)
+	switch p {
+	case -1:
+		l.pos = l.len
+	default:
+		l.pos += uint32(p)
+	}
+	return l.input[l.start:l.pos]
+}
+
+// seekDelimited Seeks the next stop, delimiter or eof byte and then return the byte slice between
+// the start position and the end byte. It does not consume the stop byte, but will consume the delimiter.
+// Returns a boolean indicating whether the stop, or eof byte was detected.
+func seekDelimited(l *Lexer, stop, delimiter byte) ([]byte, bool) {
+	l.start = l.pos
+	for {
+		switch b := l.next(); b {
+		case delimiter:
+			return l.input[l.start : l.pos-1], false
+		case stop:
+			l.pos--
+			return l.input[l.start:l.pos], true //reverse one position so as not to consume stop byte
+		case eof:
+			return l.input[l.start:l.pos], true // next does not increment pos when at eof
 		}
-		return handler(l, l.input[start:l.pos])
 	}
 }
 
@@ -378,11 +413,11 @@ func lexType(l *Lexer) stateFn {
 	case 'c':
 		l.m.Type = gostatsd.COUNTER
 		l.start = l.pos
-		return lexTypeSep
+		return lexMetricAttributes
 	case 'g':
 		l.m.Type = gostatsd.GAUGE
 		l.start = l.pos
-		return lexTypeSep
+		return lexMetricAttributes
 	case 'm':
 		if b := l.next(); b != 's' {
 			l.err = errInvalidType
@@ -390,83 +425,58 @@ func lexType(l *Lexer) stateFn {
 		}
 		l.start = l.pos
 		l.m.Type = gostatsd.TIMER
-		return lexTypeSep
+		return lexMetricAttributes
 	case 'h':
 		l.start = l.pos
 		l.m.Type = gostatsd.TIMER
-		return lexTypeSep
+		return lexMetricAttributes
 	case 's':
 		l.m.Type = gostatsd.SET
 		l.start = l.pos
-		return lexTypeSep
+		return lexMetricAttributes
 	default:
 		l.err = errInvalidType
 		return nil
-
 	}
 }
 
-// lex the possible separator between type and sampling rate.
-func lexTypeSep(l *Lexer) stateFn {
+// lexMetricAttributes iterate through any metric attributes separated by '|'
+func lexMetricAttributes(l *Lexer) stateFn {
 	b := l.next()
 	switch b {
-	case eof:
-		return nil
 	case '|':
 		l.start = l.pos
-		return lexSampleRateOrTags
+		return lexMetricAttribute
+	case eof:
+	default:
+		l.err = errInvalidType
 	}
-	l.err = errInvalidType
 	return nil
 }
 
-// lex the sample rate or the tags.
-func lexSampleRateOrTags(l *Lexer) stateFn {
+// lexMetricAttribute lex optional fields sample rate or tags. Will ignore unrecognised fields.
+func lexMetricAttribute(l *Lexer) stateFn {
 	b := l.next()
 	switch b {
 	case '@':
-		l.start = l.pos
+		input := seekUntil(l, '|')
+		v, err := strconv.ParseFloat(string(input), 64)
+		if err != nil {
+			l.err = err
+			return nil
+		}
+		l.sampling = v
+	case '#':
 		for {
-			switch b := l.next(); b {
-			case '|':
-				return lexSampleRate
-			case eof:
-				l.pos++
-				return lexSampleRate
+			data, complete := seekDelimited(l, '|', ',')
+			l.appendTag(data)
+			if complete {
+				break
 			}
 		}
-	case '#':
-		return lexTags
 	default:
-		l.err = errInvalidSamplingOrTags
-		return nil
+		// error no longer raised allow new fields to be sent but ignored
+		_ = seekUntil(l, '|')
 	}
-}
-
-// lex the sample rate.
-func lexSampleRate(l *Lexer) stateFn {
-	v, err := strconv.ParseFloat(string(l.input[l.start:l.pos-1]), 64)
-	if err != nil {
-		l.err = err
-		return nil
-	}
-	l.sampling = v
-	if l.pos >= l.len {
-		return nil
-	}
-	return lexAssert('#', lexTags)
-}
-
-// lex the tags.
-func lexTags(l *Lexer) stateFn {
-	return lexUntil(',', func(l *Lexer, data []byte) stateFn {
-		if len(data) > 0 {
-			l.tags = append(l.tags, string(data))
-		}
-		if l.pos == l.len { // eof
-			return nil
-		}
-		l.pos++ // consume comma
-		return lexTags
-	})
+	return lexMetricAttributes
 }


Test patch:
diff --git a/cmd/tester/Dockerfile b/cmd/tester/Dockerfile
index 03ba82d..8f84b3e 100644
--- a/cmd/tester/Dockerfile
+++ b/cmd/tester/Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.15.4
+FROM alpine:3.15.5
 RUN apk --no-cache add \
     ca-certificates
 ADD statsd-tester-linux /bin/statsd-tester
diff --git a/internal/lexer/lexer_test.go b/internal/lexer/lexer_test.go
index 7c7e31a..780ee51 100644
--- a/internal/lexer/lexer_test.go
+++ b/internal/lexer/lexer_test.go
@@ -1,6 +1,7 @@
 package lexer
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -13,32 +14,54 @@ import (
 func TestMetricsLexer(t *testing.T) {
 	t.Parallel()
 	tests := map[string]gostatsd.Metric{
-		"foo.bar.baz:2|c":               {Name: "foo.bar.baz", Value: 2, Type: gostatsd.COUNTER, Rate: 1.0},
-		"abc.def.g:3|g":                 {Name: "abc.def.g", Value: 3, Type: gostatsd.GAUGE, Rate: 1.0},
-		"def.g:10|ms":                   {Name: "def.g", Value: 10, Type: gostatsd.TIMER, Rate: 1.0},
-		"def.h:10|h":                    {Name: "def.h", Value: 10, Type: gostatsd.TIMER, Rate: 1.0},
-		"def.i:10|h|#foo":               {Name: "def.i", Value: 10, Type: gostatsd.TIMER, Rate: 1.0, Tags: gostatsd.Tags{"foo"}},
-		"smp.rte:5|c|@0.1":              {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 0.1},
-		"smp.rte:5|c|@0.1|#foo:bar,baz": {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 0.1, Tags: gostatsd.Tags{"foo:bar", "baz"}},
-		"smp.rte:5|c|#foo:bar,baz":      {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 1.0, Tags: gostatsd.Tags{"foo:bar", "baz"}},
-		"uniq.usr:joe|s":                {Name: "uniq.usr", StringValue: "joe", Type: gostatsd.SET, Rate: 1.0},
-		"fooBarBaz:2|c":                 {Name: "fooBarBaz", Value: 2, Type: gostatsd.COUNTER, Rate: 1.0},
-		"smp.rte:5|c|#Foo:Bar,baz":      {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 1.0, Tags: gostatsd.Tags{"Foo:Bar", "baz"}},
-		"smp.gge:1|g|#Foo:Bar":          {Name: "smp.gge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"Foo:Bar"}},
-		"smp.gge:1|g|#fo_o:ba-r":        {Name: "smp.gge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"fo_o:ba-r"}},
-		"smp gge:1|g":                   {Name: "smp_gge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
-		"smp/gge:1|g":                   {Name: "smp-gge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
-		"smp,gge$:1|g":                  {Name: "smpgge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
-		"un1qu3:john|s":                 {Name: "un1qu3", StringValue: "john", Type: gostatsd.SET, Rate: 1.0},
-		"un1qu3:john|s|#some:42":        {Name: "un1qu3", StringValue: "john", Type: gostatsd.SET, Rate: 1.0, Tags: gostatsd.Tags{"some:42"}},
-		"da-sh:1|s":                     {Name: "da-sh", StringValue: "1", Type: gostatsd.SET, Rate: 1.0},
-		"under_score:1|s":               {Name: "under_score", StringValue: "1", Type: gostatsd.SET, Rate: 1.0},
-		"a:1|g|#f,,":                    {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"f"}},
-		"a:1|g|#,,f":                    {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"f"}},
-		"a:1|g|#f,,z":                   {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"f", "z"}},
-		"a:1|g|#":                       {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
-		"a:1|g|#,":                      {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
-		"a:1|g|#,,":                     {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"foo.bar.baz:2|c":                             {Name: "foo.bar.baz", Value: 2, Type: gostatsd.COUNTER, Rate: 1.0},
+		"abc.def.g:3|g":                               {Name: "abc.def.g", Value: 3, Type: gostatsd.GAUGE, Rate: 1.0},
+		"def.g:10|ms":                                 {Name: "def.g", Value: 10, Type: gostatsd.TIMER, Rate: 1.0},
+		"def.h:10|h":                                  {Name: "def.h", Value: 10, Type: gostatsd.TIMER, Rate: 1.0},
+		"def.i:10|h|#foo":                             {Name: "def.i", Value: 10, Type: gostatsd.TIMER, Rate: 1.0, Tags: gostatsd.Tags{"foo"}},
+		"smp.rte:5|c|@0.1":                            {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 0.1},
+		"smp.rte:5|c|@0.1|#foo:bar,baz":               {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 0.1, Tags: gostatsd.Tags{"foo:bar", "baz"}},
+		"smp.rte:5|c|#foo:bar,baz":                    {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 1.0, Tags: gostatsd.Tags{"foo:bar", "baz"}},
+		"uniq.usr:joe|s":                              {Name: "uniq.usr", StringValue: "joe", Type: gostatsd.SET, Rate: 1.0},
+		"fooBarBaz:2|c":                               {Name: "fooBarBaz", Value: 2, Type: gostatsd.COUNTER, Rate: 1.0},
+		"smp.rte:5|c|#Foo:Bar,baz":                    {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 1.0, Tags: gostatsd.Tags{"Foo:Bar", "baz"}},
+		"smp.gge:1|g|#Foo:Bar":                        {Name: "smp.gge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"Foo:Bar"}},
+		"smp.gge:1|g|#fo_o:ba-r":                      {Name: "smp.gge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"fo_o:ba-r"}},
+		"smp gge:1|g":                                 {Name: "smp_gge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"smp/gge:1|g":                                 {Name: "smp-gge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"smp,gge$:1|g":                                {Name: "smpgge", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"un1qu3:john|s":                               {Name: "un1qu3", StringValue: "john", Type: gostatsd.SET, Rate: 1.0},
+		"un1qu3:john|s|#some:42":                      {Name: "un1qu3", StringValue: "john", Type: gostatsd.SET, Rate: 1.0, Tags: gostatsd.Tags{"some:42"}},
+		"da-sh:1|s":                                   {Name: "da-sh", StringValue: "1", Type: gostatsd.SET, Rate: 1.0},
+		"under_score:1|s":                             {Name: "under_score", StringValue: "1", Type: gostatsd.SET, Rate: 1.0},
+		"a:1|g|#f,,":                                  {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"f"}},
+		"a:1|g|#,,f":                                  {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"f"}},
+		"a:1|g|#f,,z":                                 {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"f", "z"}},
+		"a:1|g|#":                                     {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"a:1|g|#,":                                    {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"a:1|g|#,,":                                   {Name: "a", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"foo.bar.baz:2|c|c:xyz":                       {Name: "foo.bar.baz", Value: 2, Type: gostatsd.COUNTER, Rate: 1.0},
+		"smp.rte:5|c|@0.1|c:xyz":                      {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 0.1},
+		"smp.rte:5|c|@0.1|#foo:bar,baz|c:xyz":         {Name: "smp.rte", Value: 5, Type: gostatsd.COUNTER, Rate: 0.1, Tags: gostatsd.Tags{"foo:bar", "baz"}},
+		"def.i:10|h|#foo|c:xyz":                       {Name: "def.i", Value: 10, Type: gostatsd.TIMER, Rate: 1.0, Tags: gostatsd.Tags{"foo"}},
+		"c.after.tags:1|g|#f,,|c:xyz":                 {Name: "c.after.tags", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"f"}},
+		"c.after.tags:1|g|#,,f|c:xyz":                 {Name: "c.after.tags", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"f"}},
+		"c.after.tags:1|g|#f,,z|c:xyz":                {Name: "c.after.tags", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0, Tags: gostatsd.Tags{"f", "z"}},
+		"c.after.tags:1|g|#|c:xyz":                    {Name: "c.after.tags", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"c.after.tags:1|g|#,|c:xyz":                   {Name: "c.after.tags", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"c.after.tags:1|g|#,,|c:xyz":                  {Name: "c.after.tags", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"c.after.tags:1|g|#,,|c::,#@":                 {Name: "c.after.tags", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"field.order.rev.all:1|g|c:xyz|#foo:bar|@0.1": {Name: "field.order.rev.all", Value: 1, Type: gostatsd.GAUGE, Rate: 0.1, Tags: gostatsd.Tags{"foo:bar"}},
+		"field.order.rev.notags:1|g|c:xyz|@0.1":       {Name: "field.order.rev.notags", Value: 1, Type: gostatsd.GAUGE, Rate: 0.1},
+		"new.last.prefix:1|g|#,,|c:xyz|x:":            {Name: "new.last.prefix", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"new.last.empty:1|g|#,|c:xyz|":                {Name: "new.last.empty", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"new.last.colon:1|g|#|c:xyz|:":                {Name: "new.last.colon", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"new.first.prefix:1|g|x:#,,|c:xyz|":           {Name: "new.first.prefix", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"new.first.empty:1|g||#,|c:xyz":               {Name: "new.first.empty", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"new.first.colon:1|g|:|#|c:xyz":               {Name: "new.first.colon", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"new.mid.prefix:1|g|#,,|x:|c:xyz":             {Name: "new.mid.prefix", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"new.mid.empty:1|g|#,||c:xyz":                 {Name: "new.mid.empty", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
+		"new.mid.colon:1|g|#|:|c:xyz":                 {Name: "new.mid.colon", Value: 1, Type: gostatsd.GAUGE, Rate: 1.0},
 	}
 
 	compareMetric(t, tests, "")
@@ -46,7 +69,12 @@ func TestMetricsLexer(t *testing.T) {
 
 func TestInvalidMetricsLexer(t *testing.T) {
 	t.Parallel()
-	failing := []string{"fOO|bar:bazkk", "foo.bar.baz:1|q", "NaN.should.be:NaN|g"}
+	failing := []string{
+		"fOO|bar:bazkk",
+		"foo.bar.baz:1|q",
+		"NaN.should.be:NaN|g",
+		"bad.sampling:1|g|@",
+	}
 	for _, tc := range failing {
 		tc := tc
 		t.Run(tc, func(t *testing.T) {
@@ -70,21 +98,37 @@ func TestEventsLexer(t *testing.T) {
 	t.Parallel()
 	//_e{title.length,text.length}:title|text|d:date_happened|h:hostname|p:priority|t:alert_type|#tag1,tag2
 	tests := map[string]gostatsd.Event{
-		"_e{1,1}:a|b":                                               {Title: "a", Text: "b"},
-		"_e{6,18}:ab|| c|hello,\\nmy friend!":                       {Title: "ab|| c", Text: "hello,\nmy friend!"},
-		"_e{1,1}:a|b|d:123123":                                      {Title: "a", Text: "b", DateHappened: 123123},
-		"_e{1,1}:a|b|d:123123|h:hoost":                              {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost"},
-		"_e{1,1}:a|b|d:123123|h:hoost|p:low":                        {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow},
-		"_e{1,1}:a|b|d:123123|h:hoost|p:low|t:warning":              {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning},
-		"_e{1,1}:a|b|d:123123|h:hoost|p:low|t:warning|#tag1,t:tag2": {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
-		"_e{1,1}:a|b|t:warning|d:123123|h:hoost|p:low|#tag1,t:tag2": {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
-		"_e{1,1}:a|b|p:low|t:warning|d:123123|h:hoost|#tag1,t:tag2": {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
-		"_e{1,1}:a|b|h:hoost|p:low|t:warning|d:123123|#tag1,t:tag2": {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
-
-		"_e{1,1}:a|b|h:hoost":      {Title: "a", Text: "b", Source: "hoost"},
-		"_e{1,1}:a|b|p:low":        {Title: "a", Text: "b", Priority: gostatsd.PriLow},
-		"_e{1,1}:a|b|t:warning":    {Title: "a", Text: "b", AlertType: gostatsd.AlertWarning},
-		"_e{1,1}:a|b|#tag1,t:tag2": {Title: "a", Text: "b", Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b":                                                           {Title: "a", Text: "b"},
+		"_e{6,18}:ab|| c|hello,\\nmy friend!":                                   {Title: "ab|| c", Text: "hello,\nmy friend!"},
+		"_e{1,1}:a|b|d:123123":                                                  {Title: "a", Text: "b", DateHappened: 123123},
+		"_e{1,1}:a|b|d:123123|h:hoost":                                          {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost"},
+		"_e{1,1}:a|b|d:123123|h:hoost|p:low":                                    {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow},
+		"_e{1,1}:a|b|d:123123|h:hoost|p:low|t:warning":                          {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning},
+		"_e{1,1}:a|b|d:123123|h:hoost|p:low|t:warning|c:xyz":                    {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning},
+		"_e{1,1}:a|b|d:123123|h:hoost|p:low|t:warning|#tag1,t:tag2":             {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|d:123123|h:hoost|p:low|t:warning|#tag1,t:tag2|c:xyz":       {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|t:warning|d:123123|h:hoost|p:low|c:xyz|#tag1,t:tag2":       {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|#tag1,t:tag2|t:warning|d:123123|h:hoost|p:low|c:xyz":       {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|c:xyz|#tag1,t:tag2|t:warning|d:123123|h:hoost|p:low":       {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|p:low|c:xyz|#tag1,t:tag2|t:warning|d:123123|h:hoost":       {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|h:hoost|p:low|c:xyz|#tag1,t:tag2|t:warning|d:123123":       {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|t:warning|d:123123|h:hoost|p:low|c:xyz|#tag1,t:tag2|x:unk": {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|x:unk|#tag1,t:tag2|t:warning|d:123123|h:hoost|p:low|c:xyz": {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|c:xyz|x:unk|#tag1,t:tag2|t:warning|d:123123|h:hoost|p:low": {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|p:low|c:xyz|x:unk|#tag1,t:tag2|t:warning|d:123123|h:hoost": {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|h:hoost|p:low|c:xyz|x:unk|#tag1,t:tag2|t:warning|d:123123": {Title: "a", Text: "b", DateHappened: 123123, Source: "hoost", Priority: gostatsd.PriLow, AlertType: gostatsd.AlertWarning, Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|k:agg_key":                                                 {Title: "a", Text: "b", AggregationKey: "agg_key"},
+		"_e{1,1}:a|b|h:hoost":                                                   {Title: "a", Text: "b", Source: "hoost"},
+		"_e{1,1}:a|b|h:hoost|x:unk":                                             {Title: "a", Text: "b", Source: "hoost"},
+		"_e{1,1}:a|b|p:low":                                                     {Title: "a", Text: "b", Priority: gostatsd.PriLow},
+		"_e{1,1}:a|b|p:low|x:unk":                                               {Title: "a", Text: "b", Priority: gostatsd.PriLow},
+		"_e{1,1}:a|b|t:warning":                                                 {Title: "a", Text: "b", AlertType: gostatsd.AlertWarning},
+		"_e{1,1}:a|b|t:warning|x:unk":                                           {Title: "a", Text: "b", AlertType: gostatsd.AlertWarning},
+		"_e{1,1}:a|b|#tag1,t:tag2":                                              {Title: "a", Text: "b", Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|#tag1,t:tag2|x:unk":                                        {Title: "a", Text: "b", Tags: []string{"tag1", "t:tag2"}},
+		"_e{1,1}:a|b|c:xyz":                                                     {Title: "a", Text: "b"},
+		"_e{1,1}:a|b|c:xyz|x:unk":                                               {Title: "a", Text: "b"},
+		"_e{1,1}:a|b|x:unk":                                                     {Title: "a", Text: "b"},
 		"_e{20,34}:Deployment completed|Deployment completed in 7 minutes.|d:1463746133|h:9c00cf070c14|s:Micros Server|t:success|#topic:service.deploy,message_env:pdev,service_id:node-refapp-ci-internal,deployment_id:72e95b0f-37b0-4cf9-8e92-3e47d006b63f": {
 			Title:          "Deployment completed",
 			Text:           "Deployment completed in 7 minutes.",
@@ -141,6 +185,108 @@ func TestInvalidEventsLexer(t *testing.T) {
 	}
 }
 
+func TestSeekDelimited(t *testing.T) {
+	var stop byte = '|'
+	var delimiter byte = ','
+	var otherChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;/\\\".:()-_=?![]'~@$%^&*#"
+	var testCases = []struct {
+		name     string
+		input    []byte
+		output   [][]byte
+		nextByte byte
+	}{
+		{"all chars", []byte(otherChars), results([]byte(otherChars)), eof},
+		{"all chars + stop", []byte(fmt.Sprintf("%s|", otherChars)), results([]byte(otherChars)), stop},
+		{"empty", []byte(""), results([]byte("")), eof},
+		{"stop", []byte("|"), results([]byte("")), stop},
+		{"multiple stop", []byte("||"), results([]byte("")), stop},
+		{"stop + char", []byte("|x"), results([]byte("")), stop},
+		{"stop + char |", []byte("|x"), results([]byte("")), stop},
+		{"stop + chars", []byte("|foo.bar"), results([]byte("")), stop},
+		{"stop + chars + stop", []byte("|foo.bar|"), results([]byte("")), stop},
+		{"char", []byte("x"), results([]byte("x")), eof},
+		{"char + stop", []byte("x|"), results([]byte("x")), stop},
+		{"char + stop + char", []byte("x|y"), results([]byte("x")), stop},
+		{"chars", []byte("foo.bar"), results([]byte("foo.bar")), eof},
+		{"chars + stop", []byte("foo.bar|"), results([]byte("foo.bar")), stop},
+		{"chars + stop + chars", []byte("foo:bar|baz:quux"), results([]byte("foo:bar")), stop},
+		{"delimited", []byte("x,foo:bar,baz:quux"), results([]byte("x"), []byte("foo:bar"), []byte("baz:quux")), eof},
+		{"delimited + stop", []byte("x,foo:bar,baz:quux|y"), results([]byte("x"), []byte("foo:bar"), []byte("baz:quux")), stop},
+		{"empty delimiter", []byte(",foo:bar,,baz:quux,"), results([]byte(""), []byte("foo:bar"), []byte(""), []byte("baz:quux"), []byte("")), eof},
+		{"empty delimiter + stop", []byte(",foo:bar,,baz:quux,|y"), results([]byte(""), []byte("foo:bar"), []byte(""), []byte("baz:quux"), []byte("")), stop},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			l := newTestLexer(test.input, "")
+			result := make([][]byte, 0)
+			for {
+				data, complete := seekDelimited(l, stop, delimiter)
+				result = append(result, data)
+				if complete {
+					break
+				}
+			}
+
+			assert.Equal(t, test.output, result)
+			assert.Equal(t, test.nextByte, l.next())
+		})
+	}
+}
+
+func results(results ...[]byte) [][]byte {
+	return results
+}
+
+func TestSeekUntil(t *testing.T) {
+	var stop byte = '|'
+	var otherChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;:/\\\".,()-_=?![]'~@$%^&*#"
+	var testCases = []struct {
+		name     string
+		input    []byte
+		output   []byte
+		nextByte byte
+	}{
+		{"all chars", []byte(otherChars), []byte(otherChars), eof},
+		{"all chars + stop", []byte(fmt.Sprintf("%s|", otherChars)), []byte(otherChars), stop},
+		{"empty", []byte(""), []byte(""), eof},
+		{"stop", []byte("|"), []byte(""), stop},
+		{"multiple stop", []byte("||"), []byte(""), stop},
+		{"stop + char", []byte("|x"), []byte(""), stop},
+		{"stop + char |", []byte("|x"), []byte(""), stop},
+		{"stop + chars", []byte("|foo.bar"), []byte(""), stop},
+		{"stop + chars + stop", []byte("|foo.bar|"), []byte(""), stop},
+		{"char", []byte("x"), []byte("x"), eof},
+		{"char + stop", []byte("x|"), []byte("x"), stop},
+		{"char + stop + char", []byte("x|y"), []byte("x"), stop},
+		{"chars", []byte("foo.bar"), []byte("foo.bar"), eof},
+		{"chars + stop", []byte("foo.bar|"), []byte("foo.bar"), stop},
+		{"chars + stop + chars", []byte("foo.bar|baz:quux"), []byte("foo.bar"), stop},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			l := newTestLexer(test.input, "")
+			result := seekUntil(l, stop)
+			assert.Equal(t, test.output, result)
+			assert.Equal(t, test.nextByte, l.next())
+		})
+	}
+}
+
+func newTestLexer(input []byte, namespace string) *Lexer {
+	l := Lexer{
+		MetricPool: pool.NewMetricPool(0),
+	}
+	l.reset()
+	l.input = input
+	l.namespace = namespace
+	l.len = uint32(len(l.input))
+	l.sampling = float64(1)
+
+	return &l
+}
+
 func parseLine(input []byte, namespace string) (*gostatsd.Metric, *gostatsd.Event, error) {
 	l := Lexer{
 		MetricPool: pool.NewMetricPool(0),
@@ -211,3 +357,31 @@ func BenchmarkParseCounterWithDefaultTagsAndTags(b *testing.B) {
 func BenchmarkParseCounterWithDefaultTagsAndTagsAndNameSpace(b *testing.B) {
 	benchmarkLexer("stats", "foo.bar.baz:2|c|#foo:bar,baz", b)
 }
+func BenchmarkParseCounterWithTagsAndContainer(b *testing.B) {
+	benchmarkLexer("", "smp.rte:5|c|#foo:bar,baz|c:id", b)
+}
+func BenchmarkParseCounterWithTagsAndSampleRateAndContainer(b *testing.B) {
+	benchmarkLexer("", "smp.rte:5|c|@0.1|#foo:bar,baz|c:id", b)
+}
+
+var parselineEventBlackhole *gostatsd.Event
+
+func benchmarkEventLexer(ns string, input string, b *testing.B) {
+	slice := []byte(input)
+	var e *gostatsd.Event
+
+	l := &Lexer{
+		MetricPool: pool.NewMetricPool(0),
+	}
+
+	b.ReportAllocs()
+	b.ResetTimer()
+	for n := 0; n < b.N; n++ {
+		_, e, _ = l.Run(slice, ns)
+	}
+	parselineEventBlackhole = e
+}
+
+func BenchmarkParseEvent(b *testing.B) {
+	benchmarkEventLexer("", "_e{1,1}:a|b|d:123123|h:hoost|p:low|t:warning|#tag1,t:tag2", b)
+}