diff --git a/lib/protoparser/graphite/parser.go b/lib/protoparser/graphite/parser.go index 5c1643777..c8d08c1cf 100644 --- a/lib/protoparser/graphite/parser.go +++ b/lib/protoparser/graphite/parser.go @@ -9,6 +9,10 @@ import ( "github.com/valyala/fastjson/fastfloat" ) +// graphite text line protocol may use white space or tab as separator +// See https://github.com/grobian/carbon-c-relay/commit/f3ffe6cc2b52b07d14acbda649ad3fd6babdd528 +const graphiteSeparators = " \t" + // Rows contains parsed graphite rows. type Rows struct { Rows []Row @@ -84,9 +88,9 @@ func (r *Row) UnmarshalMetricAndTags(s string, tagsPool []Tag) ([]Tag, error) { func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) { r.reset() - n := strings.IndexByte(s, ' ') + n := strings.IndexAny(s, graphiteSeparators) if n < 0 { - return tagsPool, fmt.Errorf("cannot find whitespace between metric and value in %q", s) + return tagsPool, fmt.Errorf("cannot find separator between metric and value in %q", s) } metricAndTags := s[:n] tail := s[n+1:] @@ -96,7 +100,7 @@ func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) { return tagsPool, err } - n = strings.IndexByte(tail, ' ') + n = strings.IndexAny(tail, graphiteSeparators) if n < 0 { // There is no timestamp. Use default timestamp instead. v, err := fastfloat.Parse(tail) diff --git a/lib/protoparser/graphite/parser_test.go b/lib/protoparser/graphite/parser_test.go index 85e2cb76e..da986ec2e 100644 --- a/lib/protoparser/graphite/parser_test.go +++ b/lib/protoparser/graphite/parser_test.go @@ -1,10 +1,11 @@ package graphite import ( - "github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime" "reflect" "strings" "testing" + + "github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime" ) func TestUnmarshalMetricAndTagsFailure(t *testing.T) { @@ -258,6 +259,35 @@ func TestRowsUnmarshalSuccess(t *testing.T) { }, }, }) + + // With tab as separator + // See https://github.com/grobian/carbon-c-relay/commit/f3ffe6cc2b52b07d14acbda649ad3fd6babdd528 + f("foo.baz\t125.456\t1789\n", &Rows{ + Rows: []Row{{ + Metric: "foo.baz", + Value: 125.456, + Timestamp: 1789, + }}, + }) + // With tab as separator and tags + f("foo;baz=bar;bb=;y=x;=z\t1\t2", &Rows{ + Rows: []Row{{ + Metric: "foo", + Tags: []Tag{ + { + Key: "baz", + Value: "bar", + }, + { + Key: "y", + Value: "x", + }, + }, + Value: 1, + Timestamp: 2, + }}, + }) + } func Test_streamContext_Read(t *testing.T) {