diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 07354829e3..2729cb1092 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -27,6 +27,7 @@ sort: 15 * BUGFIX: [vmrestore](https://docs.victoriametrics.com/vmrestore.html): properly resume downloading for partially downloaded big files. Previously such files were re-downloaded from the beginning after the interrupt. Now only the remaining parts of the file are downloaded. This allows saving network bandwidth. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/487). * BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): do not store the last query across vmui page reloads. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1694). * BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix `Cannot read properties of undefined` error at table view. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1797). +* BUGFIX: properly parse Graphite plaintext lines with whitespace after the timestamp. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1865). ## [v1.69.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.69.0) diff --git a/lib/protoparser/graphite/parser.go b/lib/protoparser/graphite/parser.go index c8d08c1cf1..e62bbd6a76 100644 --- a/lib/protoparser/graphite/parser.go +++ b/lib/protoparser/graphite/parser.go @@ -114,15 +114,32 @@ func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) { if err != nil { return tagsPool, fmt.Errorf("cannot unmarshal value from %q: %w", tail[:n], err) } - ts, err := fastfloat.Parse(tail[n+1:]) + tail = stripTailSpace(tail[n+1:]) + ts, err := fastfloat.Parse(tail) if err != nil { - return tagsPool, fmt.Errorf("cannot unmarshal timestamp from %q: %w", tail[n+1:], err) + return tagsPool, fmt.Errorf("cannot unmarshal timestamp from %q: %w", tail, err) } r.Value = v r.Timestamp = int64(ts) return tagsPool, nil } +func stripTailSpace(s string) string { + n := len(s) + for { + n-- + if n < 0 { + return "" + } + ch := s[n] + // graphite text line protocol may use white space or tab as separator + // See https://github.com/grobian/carbon-c-relay/commit/f3ffe6cc2b52b07d14acbda649ad3fd6babdd528 + if ch != ' ' && ch != '\t' { + return s[:n+1] + } + } +} + func unmarshalRows(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag) { for len(s) > 0 { n := strings.IndexByte(s, '\n') diff --git a/lib/protoparser/graphite/parser_test.go b/lib/protoparser/graphite/parser_test.go index da986ec2ef..24c8ab9dab 100644 --- a/lib/protoparser/graphite/parser_test.go +++ b/lib/protoparser/graphite/parser_test.go @@ -288,6 +288,23 @@ func TestRowsUnmarshalSuccess(t *testing.T) { }}, }) + // Whitespace after the timestamp + // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1865 + f("foo.baz 125 1789 \na 1.34 567\t ", &Rows{ + Rows: []Row{ + { + Metric: "foo.baz", + Value: 125, + Timestamp: 1789, + }, + { + Metric: "a", + Value: 1.34, + Timestamp: 567, + }, + }, + }) + } func Test_streamContext_Read(t *testing.T) {