app/vminsert/opentsdb: skip invalid rows and continue parsing the remaining rows

Invalid rows are logged and counted in `vm_rows_invalid_total{type="opentsdb"}` metric
This commit is contained in:
Aliaksandr Valialkin 2019-08-24 12:54:17 +03:00
parent 2a8fc41bab
commit 38b9615c53
4 changed files with 74 additions and 50 deletions

View file

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/fastjson/fastfloat" "github.com/valyala/fastjson/fastfloat"
) )
@ -34,10 +36,8 @@ func (rs *Rows) Reset() {
// See http://opentsdb.net/docs/build/html/api_telnet/put.html // See http://opentsdb.net/docs/build/html/api_telnet/put.html
// //
// s must be unchanged until rs is in use. // s must be unchanged until rs is in use.
func (rs *Rows) Unmarshal(s string) error { func (rs *Rows) Unmarshal(s string) {
var err error rs.Rows, rs.tagsPool = unmarshalRows(rs.Rows[:0], s, rs.tagsPool[:0])
rs.Rows, rs.tagsPool, err = unmarshalRows(rs.Rows[:0], s, rs.tagsPool[:0])
return err
} }
// Row is a single OpenTSDB row. // Row is a single OpenTSDB row.
@ -89,41 +89,46 @@ func (r *Row) unmarshal(s string, tagsPool []Tag) ([]Tag, error) {
return tagsPool, nil return tagsPool, nil
} }
func unmarshalRows(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag, error) { func unmarshalRows(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag) {
for len(s) > 0 { for len(s) > 0 {
n := strings.IndexByte(s, '\n') n := strings.IndexByte(s, '\n')
if n == 0 { if n < 0 {
// Skip empty line // The last line.
s = s[1:] return unmarshalRow(dst, s, tagsPool)
continue
} }
dst, tagsPool = unmarshalRow(dst, s[:n], tagsPool)
s = s[n+1:]
}
return dst, tagsPool
}
func unmarshalRow(dst []Row, s string, tagsPool []Tag) ([]Row, []Tag) {
if len(s) > 0 && s[len(s)-1] == '\r' {
s = s[:len(s)-1]
}
if len(s) == 0 {
// Skip empty line
return dst, tagsPool
}
if cap(dst) > len(dst) { if cap(dst) > len(dst) {
dst = dst[:len(dst)+1] dst = dst[:len(dst)+1]
} else { } else {
dst = append(dst, Row{}) dst = append(dst, Row{})
} }
r := &dst[len(dst)-1] r := &dst[len(dst)-1]
if n < 0 {
// The last line.
var err error var err error
tagsPool, err = r.unmarshal(s, tagsPool) tagsPool, err = r.unmarshal(s, tagsPool)
if err != nil { if err != nil {
err = fmt.Errorf("cannot unmarshal OpenTSDB line %q: %s", s, err) dst = dst[:len(dst)-1]
return dst, tagsPool, err logger.Errorf("cannot unmarshal OpenTSDB line %q: %s", s, err)
invalidLines.Inc()
} }
return dst, tagsPool, nil return dst, tagsPool
}
var err error
tagsPool, err = r.unmarshal(s[:n], tagsPool)
if err != nil {
err = fmt.Errorf("cannot unmarshal OpenTSDB line %q: %s", s[:n], err)
return dst, tagsPool, err
}
s = s[n+1:]
}
return dst, tagsPool, nil
} }
var invalidLines = metrics.NewCounter(`vm_rows_invalid_total{type="opentsdb"}`)
func unmarshalTags(dst []Tag, s string) ([]Tag, error) { func unmarshalTags(dst []Tag, s string) ([]Tag, error) {
for { for {
if cap(dst) > len(dst) { if cap(dst) > len(dst) {

View file

@ -9,13 +9,15 @@ func TestRowsUnmarshalFailure(t *testing.T) {
f := func(s string) { f := func(s string) {
t.Helper() t.Helper()
var rows Rows var rows Rows
if err := rows.Unmarshal(s); err == nil { rows.Unmarshal(s)
t.Fatalf("expecting non-nil error when parsing %q", s) if len(rows.Rows) != 0 {
t.Fatalf("unexpected number of rows parsed; got %d; want 0", len(rows.Rows))
} }
// Try again // Try again
if err := rows.Unmarshal(s); err == nil { rows.Unmarshal(s)
t.Fatalf("expecting non-nil error when parsing %q", s) if len(rows.Rows) != 0 {
t.Fatalf("unexpected number of rows parsed; got %d; want 0", len(rows.Rows))
} }
} }
@ -51,17 +53,13 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
f := func(s string, rowsExpected *Rows) { f := func(s string, rowsExpected *Rows) {
t.Helper() t.Helper()
var rows Rows var rows Rows
if err := rows.Unmarshal(s); err != nil { rows.Unmarshal(s)
t.Fatalf("cannot unmarshal %q: %s", s, err)
}
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) { if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows) t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
} }
// Try unmarshaling again // Try unmarshaling again
if err := rows.Unmarshal(s); err != nil { rows.Unmarshal(s)
t.Fatalf("cannot unmarshal %q: %s", s, err)
}
if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) { if !reflect.DeepEqual(rows.Rows, rowsExpected.Rows) {
t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows) t.Fatalf("unexpected rows;\ngot\n%+v;\nwant\n%+v", rows.Rows, rowsExpected.Rows)
} }
@ -74,7 +72,9 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
// Empty line // Empty line
f("", &Rows{}) f("", &Rows{})
f("\r", &Rows{})
f("\n\n", &Rows{}) f("\n\n", &Rows{})
f("\n\r\n", &Rows{})
// Single line // Single line
f("put foobar 789 -123.456 a=b", &Rows{ f("put foobar 789 -123.456 a=b", &Rows{
@ -200,4 +200,27 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
}, },
}, },
}) })
// Multi lines with invalid line
f("put foo 2 0.3 a=b\naaa bbb\nput bar.baz 43 0.34 a=b\n", &Rows{
Rows: []Row{
{
Metric: "foo",
Value: 0.3,
Timestamp: 2,
Tags: []Tag{{
Key: "a",
Value: "b",
}},
},
{
Metric: "bar.baz",
Value: 0.34,
Timestamp: 43,
Tags: []Tag{{
Key: "a",
Value: "b",
}},
},
},
})
} }

View file

@ -16,8 +16,9 @@ put cpu.usage_irq 1234556768 0.34432 a=b
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var rows Rows var rows Rows
for pb.Next() { for pb.Next() {
if err := rows.Unmarshal(s); err != nil { rows.Unmarshal(s)
panic(fmt.Errorf("cannot unmarshal %q: %s", s, err)) if len(rows.Rows) != 4 {
panic(fmt.Errorf("unexpected number of parsed rows; got %d; want 4", len(rows.Rows)))
} }
} }
}) })

View file

@ -103,11 +103,7 @@ func (ctx *pushCtx) Read(r io.Reader) bool {
return false return false
} }
} }
if err := ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf)); err != nil { ctx.Rows.Unmarshal(bytesutil.ToUnsafeString(ctx.reqBuf))
opentsdbUnmarshalErrors.Inc()
ctx.err = fmt.Errorf("cannot unmarshal OpenTSDB put protocol data with size %d: %s", len(ctx.reqBuf), err)
return false
}
// Fill in missing timestamps // Fill in missing timestamps
currentTimestamp := time.Now().Unix() currentTimestamp := time.Now().Unix()
@ -155,7 +151,6 @@ func (ctx *pushCtx) reset() {
var ( var (
opentsdbReadCalls = metrics.NewCounter(`vm_read_calls_total{name="opentsdb"}`) opentsdbReadCalls = metrics.NewCounter(`vm_read_calls_total{name="opentsdb"}`)
opentsdbReadErrors = metrics.NewCounter(`vm_read_errors_total{name="opentsdb"}`) opentsdbReadErrors = metrics.NewCounter(`vm_read_errors_total{name="opentsdb"}`)
opentsdbUnmarshalErrors = metrics.NewCounter(`vm_unmarshal_errors_total{name="opentsdb"}`)
) )
func getPushCtx() *pushCtx { func getPushCtx() *pushCtx {