2021-09-28 19:47:45 +00:00
|
|
|
package datadog
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-07-06 22:30:56 +00:00
|
|
|
"strings"
|
2021-09-28 19:47:45 +00:00
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
|
|
|
)
|
|
|
|
|
2022-07-06 22:30:56 +00:00
|
|
|
// SplitTag splits DataDog tag into tag name and value.
|
|
|
|
//
|
|
|
|
// See https://docs.datadoghq.com/getting_started/tagging/#define-tags
|
|
|
|
func SplitTag(tag string) (string, string) {
|
|
|
|
n := strings.IndexByte(tag, ':')
|
|
|
|
if n < 0 {
|
|
|
|
// No tag value.
|
|
|
|
return tag, "no_label_value"
|
|
|
|
}
|
|
|
|
return tag[:n], tag[n+1:]
|
|
|
|
}
|
|
|
|
|
2021-09-28 19:47:45 +00:00
|
|
|
// Request represents DataDog POST request to /api/v1/series
|
|
|
|
//
|
|
|
|
// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
|
|
|
|
type Request struct {
|
|
|
|
Series []Series `json:"series"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (req *Request) reset() {
|
2022-12-11 21:09:23 +00:00
|
|
|
// 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]
|
2021-09-28 19:47:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal unmarshals DataDog /api/v1/series request body from b to req.
|
|
|
|
//
|
|
|
|
// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
|
|
|
|
//
|
|
|
|
// b shouldn't be modified when req is in use.
|
|
|
|
func (req *Request) Unmarshal(b []byte) error {
|
|
|
|
req.reset()
|
|
|
|
if err := json.Unmarshal(b, req); err != nil {
|
|
|
|
return fmt.Errorf("cannot unmarshal %q: %w", b, err)
|
|
|
|
}
|
|
|
|
// Set missing timestamps to the current time.
|
|
|
|
currentTimestamp := float64(fasttime.UnixTimestamp())
|
|
|
|
series := req.Series
|
|
|
|
for i := range series {
|
|
|
|
points := series[i].Points
|
|
|
|
for j := range points {
|
|
|
|
if points[j][0] <= 0 {
|
|
|
|
points[j][0] = currentTimestamp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Series represents a series item from DataDog POST request to /api/v1/series
|
|
|
|
//
|
|
|
|
// See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
|
|
|
|
type Series struct {
|
2022-12-11 21:09:23 +00:00
|
|
|
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"`
|
2021-09-28 19:47:45 +00:00
|
|
|
|
|
|
|
// Do not decode Interval, since it isn't used by VictoriaMetrics
|
|
|
|
// Interval int64 `json:"interval"`
|
|
|
|
|
|
|
|
Points []Point `json:"points"`
|
|
|
|
Tags []string `json:"tags"`
|
|
|
|
|
|
|
|
// Do not decode Type, since it isn't used by VictoriaMetrics
|
|
|
|
// Type string `json:"type"`
|
|
|
|
}
|
|
|
|
|
2022-12-11 21:09:23 +00:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
|
2021-09-28 19:47:45 +00:00
|
|
|
// Point represents a point from DataDog POST request to /api/v1/series
|
|
|
|
type Point [2]float64
|
|
|
|
|
|
|
|
// Timestamp returns timestamp in milliseconds from the given pt.
|
|
|
|
func (pt *Point) Timestamp() int64 {
|
|
|
|
return int64(pt[0] * 1000)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Value returns value from the given pt.
|
|
|
|
func (pt *Point) Value() float64 {
|
|
|
|
return pt[1]
|
|
|
|
}
|