From f4fcf3b57286dddd90ef6774ad7d8cb5627502ff Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Sun, 11 Dec 2022 13:09:23 -0800 Subject: [PATCH] lib/protoparser/datadog: do not re-use previously parsed field values if they are missing in the currently parsed message Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3432 --- docs/CHANGELOG.md | 3 +++ lib/protoparser/datadog/parser.go | 37 +++++++++++++++++++++++--- lib/protoparser/datadog/parser_test.go | 37 ++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5873dc3c3..0db1dfd42 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -15,6 +15,9 @@ The following tip changes can be tested by building VictoriaMetrics components f ## v1.79.x long-time support release (LTS) +* BUGFIX: [DataDog protocol parser](https://docs.victoriametrics.com/#how-to-send-data-from-datadog-agent): do not re-use `host` and `device` fields from the previously parsed messages if these fields are missing in the currently parsed message. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3432). + + ## [v1.79.6](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.79.6) Released at 11-12-2022 diff --git a/lib/protoparser/datadog/parser.go b/lib/protoparser/datadog/parser.go index 931617d5e..c32a9c99d 100644 --- a/lib/protoparser/datadog/parser.go +++ b/lib/protoparser/datadog/parser.go @@ -28,7 +28,15 @@ type Request struct { } func (req *Request) reset() { - req.Series = req.Series[:0] + // recursively reset all the fields in req in order to avoid field value + // re-use in json.Unmarshal() when the corresponding field is missing + // in the unmarshaled JSON. + // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3432 + series := req.Series + for i := range series { + series[i].reset() + } + req.Series = series[:0] } // Unmarshal unmarshals DataDog /api/v1/series request body from b to req. @@ -59,12 +67,17 @@ func (req *Request) Unmarshal(b []byte) error { // // See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics type Series struct { - Host string `json:"host"` + Metric string `json:"metric"` + Host string `json:"host"` + + // The device field does not appear in the datadog docs, but datadog-agent does use it. + // Datadog agent (v7 at least), removes the tag "device" and adds it as its own field. Why? That I don't know! + // https://github.com/DataDog/datadog-agent/blob/0ada7a97fed6727838a6f4d9c87123d2aafde735/pkg/metrics/series.go#L84-L105 + Device string `json:"device"` // Do not decode Interval, since it isn't used by VictoriaMetrics // Interval int64 `json:"interval"` - Metric string `json:"metric"` Points []Point `json:"points"` Tags []string `json:"tags"` @@ -72,6 +85,24 @@ type Series struct { // Type string `json:"type"` } +func (s *Series) reset() { + s.Metric = "" + s.Host = "" + s.Device = "" + + points := s.Points + for i := range points { + points[i] = Point{} + } + s.Points = points[:0] + + tags := s.Tags + for i := range tags { + tags[i] = "" + } + s.Tags = tags[:0] +} + // Point represents a point from DataDog POST request to /api/v1/series type Point [2]float64 diff --git a/lib/protoparser/datadog/parser_test.go b/lib/protoparser/datadog/parser_test.go index 3c472d91c..1d46741a8 100644 --- a/lib/protoparser/datadog/parser_test.go +++ b/lib/protoparser/datadog/parser_test.go @@ -22,6 +22,43 @@ func TestSplitTag(t *testing.T) { f(":bar", "", "bar") } +func TestRequestUnmarshalMissingHost(t *testing.T) { + // This tests https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3432 + req := Request{ + Series: []Series{{ + Host: "prev-host", + Device: "prev-device", + }}, + } + data := ` +{ + "series": [ + { + "metric": "system.load.1", + "points": [[ + 1575317847, + 0.5 + ]] + } + ] +}` + if err := req.Unmarshal([]byte(data)); err != nil { + t.Fatalf("unexpected error: %s", err) + } + reqExpected := Request{ + Series: []Series{{ + Metric: "system.load.1", + Points: []Point{{ + 1575317847, + 0.5, + }}, + }}, + } + if !reflect.DeepEqual(&req, &reqExpected) { + t.Fatalf("unexpected request parsed;\ngot\n%+v\nwant\n%+v", req, reqExpected) + } +} + func TestRequestUnmarshalFailure(t *testing.T) { f := func(s string) { t.Helper()