mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 12:31:07 +01:00
app/{vmagent,vminsert}: follow-up after a1d1ccd6f2
- Document the change at docs/CHANGELOG.md - Copy changes from docs/Single-server-VictoriaMetrics.md to README.md - Add missing handler for processing multitenant requests ( https://docs.victoriametrics.com/vmagent/#multitenancy ) - Substitute github.com/stretchr/testify dependency with 3 lines of code in the added tests - Comment unclear code at lib/protoparser/datadogsketches/parser.go , so @AndrewChubatiuk could update it and add permalinks to the original source code there. - Various code cleanups Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5584 Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3091
This commit is contained in:
parent
4a31bd9661
commit
541b644d3d
24
README.md
24
README.md
@ -509,8 +509,9 @@ See also [vmagent](https://docs.victoriametrics.com/vmagent.html), which can be
|
|||||||
|
|
||||||
## How to send data from DataDog agent
|
## How to send data from DataDog agent
|
||||||
|
|
||||||
VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/) or [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/)
|
VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/), [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/) and
|
||||||
via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) at `/datadog/api/v2/series` path.
|
[DataDog Lambda Extension](https://docs.datadoghq.com/serverless/libraries_integrations/extension/)
|
||||||
|
via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) at `/datadog/api/v2/series` or via "sketches" API at `/datadog/api/beta/sketches`.
|
||||||
|
|
||||||
### Sending metrics to VictoriaMetrics
|
### Sending metrics to VictoriaMetrics
|
||||||
|
|
||||||
@ -536,11 +537,11 @@ add the following line:
|
|||||||
dd_url: http://victoriametrics:8428/datadog
|
dd_url: http://victoriametrics:8428/datadog
|
||||||
```
|
```
|
||||||
|
|
||||||
[vmagent](https://docs.victoriametrics.com/vmagent.html) also can accept Datadog metrics format. Depending on where vmagent will forward data,
|
[vmagent](https://docs.victoriametrics.com/vmagent.html) also can accept DataDog metrics format. Depending on where vmagent will forward data,
|
||||||
pick [single-node or cluster URL](https://docs.victoriametrics.com/url-examples.html#datadog) formats.
|
pick [single-node or cluster URL](https://docs.victoriametrics.com/url-examples.html#datadog) formats.
|
||||||
|
|
||||||
### Sending metrics to Datadog and VictoriaMetrics
|
### Sending metrics to DataDog and VictoriaMetrics
|
||||||
|
|
||||||
DataDog allows configuring [Dual Shipping](https://docs.datadoghq.com/agent/guide/dual-shipping/) for metrics
|
DataDog allows configuring [Dual Shipping](https://docs.datadoghq.com/agent/guide/dual-shipping/) for metrics
|
||||||
sending via ENV variable `DD_ADDITIONAL_ENDPOINTS` or via configuration file `additional_endpoints`.
|
sending via ENV variable `DD_ADDITIONAL_ENDPOINTS` or via configuration file `additional_endpoints`.
|
||||||
|
|
||||||
@ -564,6 +565,19 @@ additional_endpoints:
|
|||||||
- apikey
|
- apikey
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Send metrics via Serverless DataDog plugin
|
||||||
|
|
||||||
|
Disable logs (logs ingestion is not supported by VictoriaMetrics) and set a custom endpoint in `serverless.yaml`:
|
||||||
|
|
||||||
|
```
|
||||||
|
custom:
|
||||||
|
datadog:
|
||||||
|
enableDDLogs: false # Disabled not supported DD logs
|
||||||
|
apiKey: fakekey # Set any key, otherwise plugin fails
|
||||||
|
provider:
|
||||||
|
environment:
|
||||||
|
DD_DD_URL: <<vm-url>>/datadog # VictoriaMetrics endpoint for DataDog
|
||||||
|
```
|
||||||
|
|
||||||
### Send via cURL
|
### Send via cURL
|
||||||
|
|
||||||
|
@ -41,19 +41,15 @@ func insertRows(at *auth.Token, sketches []*datadogsketches.Sketch, extraLabels
|
|||||||
tssDst := ctx.WriteRequest.Timeseries[:0]
|
tssDst := ctx.WriteRequest.Timeseries[:0]
|
||||||
labels := ctx.Labels[:0]
|
labels := ctx.Labels[:0]
|
||||||
samples := ctx.Samples[:0]
|
samples := ctx.Samples[:0]
|
||||||
for i := range sketches {
|
for _, sketch := range sketches {
|
||||||
sketch := sketches[i]
|
ms := sketch.ToSummary()
|
||||||
metrics := sketch.ToHistogram()
|
for _, m := range ms {
|
||||||
rowsTotal += sketch.RowsCount()
|
|
||||||
for m := range metrics {
|
|
||||||
metric := metrics[m]
|
|
||||||
labelsLen := len(labels)
|
labelsLen := len(labels)
|
||||||
labels = append(labels, prompbmarshal.Label{
|
labels = append(labels, prompbmarshal.Label{
|
||||||
Name: "__name__",
|
Name: "__name__",
|
||||||
Value: metric.Name,
|
Value: m.Name,
|
||||||
})
|
})
|
||||||
for l := range metric.Labels {
|
for _, label := range m.Labels {
|
||||||
label := metric.Labels[l]
|
|
||||||
labels = append(labels, prompbmarshal.Label{
|
labels = append(labels, prompbmarshal.Label{
|
||||||
Name: label.Name,
|
Name: label.Name,
|
||||||
Value: label.Value,
|
Value: label.Value,
|
||||||
@ -71,13 +67,13 @@ func insertRows(at *auth.Token, sketches []*datadogsketches.Sketch, extraLabels
|
|||||||
}
|
}
|
||||||
labels = append(labels, extraLabels...)
|
labels = append(labels, extraLabels...)
|
||||||
samplesLen := len(samples)
|
samplesLen := len(samples)
|
||||||
for p := range metric.Points {
|
for _, p := range m.Points {
|
||||||
point := metric.Points[p]
|
|
||||||
samples = append(samples, prompbmarshal.Sample{
|
samples = append(samples, prompbmarshal.Sample{
|
||||||
Timestamp: sketch.Dogsketches[p].Ts * 1000,
|
Timestamp: p.Timestamp,
|
||||||
Value: point,
|
Value: p.Value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
rowsTotal += len(m.Points)
|
||||||
tssDst = append(tssDst, prompbmarshal.TimeSeries{
|
tssDst = append(tssDst, prompbmarshal.TimeSeries{
|
||||||
Labels: labels[labelsLen:],
|
Labels: labels[labelsLen:],
|
||||||
Samples: samples[samplesLen:],
|
Samples: samples[samplesLen:],
|
||||||
|
@ -613,6 +613,15 @@ func processMultitenantRequest(w http.ResponseWriter, r *http.Request, path stri
|
|||||||
w.WriteHeader(202)
|
w.WriteHeader(202)
|
||||||
fmt.Fprintf(w, `{"status":"ok"}`)
|
fmt.Fprintf(w, `{"status":"ok"}`)
|
||||||
return true
|
return true
|
||||||
|
case "datadog/api/beta/sketches":
|
||||||
|
datadogsketchesWriteRequests.Inc()
|
||||||
|
if err := datadogsketches.InsertHandlerForHTTP(at, r); err != nil {
|
||||||
|
datadogsketchesWriteErrors.Inc()
|
||||||
|
httpserver.Errorf(w, r, "%s", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
w.WriteHeader(202)
|
||||||
|
return true
|
||||||
case "datadog/api/v1/validate":
|
case "datadog/api/v1/validate":
|
||||||
datadogValidateRequests.Inc()
|
datadogValidateRequests.Inc()
|
||||||
// See https://docs.datadoghq.com/api/latest/authentication/#validate-api-key
|
// See https://docs.datadoghq.com/api/latest/authentication/#validate-api-key
|
||||||
|
@ -35,23 +35,18 @@ func insertRows(sketches []*datadogsketches.Sketch, extraLabels []prompbmarshal.
|
|||||||
defer common.PutInsertCtx(ctx)
|
defer common.PutInsertCtx(ctx)
|
||||||
|
|
||||||
rowsLen := 0
|
rowsLen := 0
|
||||||
for i := range sketches {
|
for _, sketch := range sketches {
|
||||||
sketch := sketches[i]
|
|
||||||
rowsLen += sketch.RowsCount()
|
rowsLen += sketch.RowsCount()
|
||||||
}
|
}
|
||||||
ctx.Reset(rowsLen)
|
ctx.Reset(rowsLen)
|
||||||
rowsTotal := 0
|
rowsTotal := 0
|
||||||
hasRelabeling := relabel.HasRelabeling()
|
hasRelabeling := relabel.HasRelabeling()
|
||||||
for i := range sketches {
|
for _, sketch := range sketches {
|
||||||
sketch := sketches[i]
|
ms := sketch.ToSummary()
|
||||||
metrics := sketch.ToHistogram()
|
for _, m := range ms {
|
||||||
rowsTotal += sketch.RowsCount()
|
|
||||||
for m := range metrics {
|
|
||||||
metric := metrics[m]
|
|
||||||
ctx.Labels = ctx.Labels[:0]
|
ctx.Labels = ctx.Labels[:0]
|
||||||
ctx.AddLabel("", metric.Name)
|
ctx.AddLabel("", m.Name)
|
||||||
for l := range metric.Labels {
|
for _, label := range m.Labels {
|
||||||
label := metric.Labels[l]
|
|
||||||
ctx.AddLabel(label.Name, label.Value)
|
ctx.AddLabel(label.Name, label.Value)
|
||||||
}
|
}
|
||||||
for _, tag := range sketch.Tags {
|
for _, tag := range sketch.Tags {
|
||||||
@ -75,14 +70,13 @@ func insertRows(sketches []*datadogsketches.Sketch, extraLabels []prompbmarshal.
|
|||||||
ctx.SortLabelsIfNeeded()
|
ctx.SortLabelsIfNeeded()
|
||||||
var metricNameRaw []byte
|
var metricNameRaw []byte
|
||||||
var err error
|
var err error
|
||||||
for p := range metric.Points {
|
for _, p := range m.Points {
|
||||||
value := metric.Points[p]
|
metricNameRaw, err = ctx.WriteDataPointExt(metricNameRaw, ctx.Labels, p.Timestamp, p.Value)
|
||||||
timestamp := sketch.Dogsketches[p].Ts * 1000
|
|
||||||
metricNameRaw, err = ctx.WriteDataPointExt(metricNameRaw, ctx.Labels, timestamp, value)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rowsTotal += len(m.Points)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rowsInserted.Add(rowsTotal)
|
rowsInserted.Add(rowsTotal)
|
||||||
|
@ -29,6 +29,7 @@ The sandbox cluster installation is running under the constant load generated by
|
|||||||
## tip
|
## tip
|
||||||
|
|
||||||
* FEATURE: all VictoriaMetrics components: add support for TLS client certificate verification at `-httpListenAddr` (aka [mTLS](https://en.wikipedia.org/wiki/Mutual_authentication)). See [these docs](https://docs.victoriametrics.com/#mtls-protection). See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5458).
|
* FEATURE: all VictoriaMetrics components: add support for TLS client certificate verification at `-httpListenAddr` (aka [mTLS](https://en.wikipedia.org/wiki/Mutual_authentication)). See [these docs](https://docs.victoriametrics.com/#mtls-protection). See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5458).
|
||||||
|
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html) and [single-node VictoriaMetrics](https://docs.victoriametrics.com): add support for data ingestion via [DataDog lambda extension](https://docs.datadoghq.com/serverless/libraries_integrations/extension/) aka `/api/beta/sketches` endpoint. See [these docs](https://docs.victoriametrics.com/#how-to-send-data-from-datadog-agent) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3091). Thanks to @AndrewChubatiuk for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5584).
|
||||||
* FEATURE: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): add `-disableReroutingOnUnavailable` command-line flag, which can be used for reducing resource usage spikes at `vmstorage` nodes during rolling restart. Thanks to @Muxa1L for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5713).
|
* FEATURE: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): add `-disableReroutingOnUnavailable` command-line flag, which can be used for reducing resource usage spikes at `vmstorage` nodes during rolling restart. Thanks to @Muxa1L for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5713).
|
||||||
* FEATURE: [dashboards/vmagent](https://grafana.com/grafana/dashboards/12683): add `Targets scraped/s` stat panel showing the number of targets scraped by the vmagent per-second.
|
* FEATURE: [dashboards/vmagent](https://grafana.com/grafana/dashboards/12683): add `Targets scraped/s` stat panel showing the number of targets scraped by the vmagent per-second.
|
||||||
* FEATURE: [dashboards/all](https://grafana.com/orgs/victoriametrics): add new panel `CPU spent on GC`. It should help identifying cases when too much CPU is spent on garbage collection, and advice users on how this can be addressed.
|
* FEATURE: [dashboards/all](https://grafana.com/orgs/victoriametrics): add new panel `CPU spent on GC`. It should help identifying cases when too much CPU is spent on garbage collection, and advice users on how this can be addressed.
|
||||||
|
@ -512,8 +512,9 @@ See also [vmagent](https://docs.victoriametrics.com/vmagent.html), which can be
|
|||||||
|
|
||||||
## How to send data from DataDog agent
|
## How to send data from DataDog agent
|
||||||
|
|
||||||
VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/) or [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/)
|
VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/), [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/) and
|
||||||
via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) at `/datadog/api/v2/series` path.
|
[DataDog Lambda Extension](https://docs.datadoghq.com/serverless/libraries_integrations/extension/)
|
||||||
|
via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) at `/datadog/api/v2/series` or via "sketches" API at `/datadog/api/beta/sketches`.
|
||||||
|
|
||||||
### Sending metrics to VictoriaMetrics
|
### Sending metrics to VictoriaMetrics
|
||||||
|
|
||||||
@ -539,11 +540,11 @@ add the following line:
|
|||||||
dd_url: http://victoriametrics:8428/datadog
|
dd_url: http://victoriametrics:8428/datadog
|
||||||
```
|
```
|
||||||
|
|
||||||
[vmagent](https://docs.victoriametrics.com/vmagent.html) also can accept Datadog metrics format. Depending on where vmagent will forward data,
|
[vmagent](https://docs.victoriametrics.com/vmagent.html) also can accept DataDog metrics format. Depending on where vmagent will forward data,
|
||||||
pick [single-node or cluster URL](https://docs.victoriametrics.com/url-examples.html#datadog) formats.
|
pick [single-node or cluster URL](https://docs.victoriametrics.com/url-examples.html#datadog) formats.
|
||||||
|
|
||||||
### Sending metrics to Datadog and VictoriaMetrics
|
### Sending metrics to DataDog and VictoriaMetrics
|
||||||
|
|
||||||
DataDog allows configuring [Dual Shipping](https://docs.datadoghq.com/agent/guide/dual-shipping/) for metrics
|
DataDog allows configuring [Dual Shipping](https://docs.datadoghq.com/agent/guide/dual-shipping/) for metrics
|
||||||
sending via ENV variable `DD_ADDITIONAL_ENDPOINTS` or via configuration file `additional_endpoints`.
|
sending via ENV variable `DD_ADDITIONAL_ENDPOINTS` or via configuration file `additional_endpoints`.
|
||||||
|
|
||||||
@ -567,6 +568,19 @@ additional_endpoints:
|
|||||||
- apikey
|
- apikey
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Send metrics via Serverless DataDog plugin
|
||||||
|
|
||||||
|
Disable logs (logs ingestion is not supported by VictoriaMetrics) and set a custom endpoint in `serverless.yaml`:
|
||||||
|
|
||||||
|
```
|
||||||
|
custom:
|
||||||
|
datadog:
|
||||||
|
enableDDLogs: false # Disabled not supported DD logs
|
||||||
|
apiKey: fakekey # Set any key, otherwise plugin fails
|
||||||
|
provider:
|
||||||
|
environment:
|
||||||
|
DD_DD_URL: <<vm-url>>/datadog # VictoriaMetrics endpoint for DataDog
|
||||||
|
```
|
||||||
|
|
||||||
### Send via cURL
|
### Send via cURL
|
||||||
|
|
||||||
|
@ -520,8 +520,9 @@ See also [vmagent](https://docs.victoriametrics.com/vmagent.html), which can be
|
|||||||
|
|
||||||
## How to send data from DataDog agent
|
## How to send data from DataDog agent
|
||||||
|
|
||||||
VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/), [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/) or [DataDog Lambda Extension](https://docs.datadoghq.com/serverless/libraries_integrations/extension/)
|
VictoriaMetrics accepts data from [DataDog agent](https://docs.datadoghq.com/agent/), [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/) and
|
||||||
via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) at `/datadog/api/v2/series` path or via "sketches" API at `/datadog/api/beta/sketches`.
|
[DataDog Lambda Extension](https://docs.datadoghq.com/serverless/libraries_integrations/extension/)
|
||||||
|
via ["submit metrics" API](https://docs.datadoghq.com/api/latest/metrics/#submit-metrics) at `/datadog/api/v2/series` or via "sketches" API at `/datadog/api/beta/sketches`.
|
||||||
|
|
||||||
### Sending metrics to VictoriaMetrics
|
### Sending metrics to VictoriaMetrics
|
||||||
|
|
||||||
@ -551,7 +552,7 @@ dd_url: http://victoriametrics:8428/datadog
|
|||||||
pick [single-node or cluster URL](https://docs.victoriametrics.com/url-examples.html#datadog) formats.
|
pick [single-node or cluster URL](https://docs.victoriametrics.com/url-examples.html#datadog) formats.
|
||||||
|
|
||||||
### Sending metrics to DataDog and VictoriaMetrics
|
### Sending metrics to DataDog and VictoriaMetrics
|
||||||
|
|
||||||
DataDog allows configuring [Dual Shipping](https://docs.datadoghq.com/agent/guide/dual-shipping/) for metrics
|
DataDog allows configuring [Dual Shipping](https://docs.datadoghq.com/agent/guide/dual-shipping/) for metrics
|
||||||
sending via ENV variable `DD_ADDITIONAL_ENDPOINTS` or via configuration file `additional_endpoints`.
|
sending via ENV variable `DD_ADDITIONAL_ENDPOINTS` or via configuration file `additional_endpoints`.
|
||||||
|
|
||||||
@ -575,10 +576,10 @@ additional_endpoints:
|
|||||||
- apikey
|
- apikey
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Send metrics via Serverless DataDog plugin
|
||||||
|
|
||||||
### Send via Serverless DataDog plugin
|
Disable logs (logs ingestion is not supported by VictoriaMetrics) and set a custom endpoint in `serverless.yaml`:
|
||||||
|
|
||||||
Disable logs (logs ingestion is not supported by Victoria Metrics) and set a custom endpoint in serverless.yaml
|
|
||||||
```
|
```
|
||||||
custom:
|
custom:
|
||||||
datadog:
|
datadog:
|
||||||
@ -586,7 +587,7 @@ custom:
|
|||||||
apiKey: fakekey # Set any key, otherwise plugin fails
|
apiKey: fakekey # Set any key, otherwise plugin fails
|
||||||
provider:
|
provider:
|
||||||
environment:
|
environment:
|
||||||
DD_DD_URL: <<vm-url>>/datadog # Victoria Metrics endpoint for DataDog
|
DD_DD_URL: <<vm-url>>/datadog # VictoriaMetrics endpoint for DataDog
|
||||||
```
|
```
|
||||||
|
|
||||||
### Send via cURL
|
### Send via cURL
|
||||||
|
3
go.mod
3
go.mod
@ -37,8 +37,6 @@ require (
|
|||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.8.4
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.112.0 // indirect
|
cloud.google.com/go v0.112.0 // indirect
|
||||||
cloud.google.com/go/compute v1.23.4 // indirect
|
cloud.google.com/go/compute v1.23.4 // indirect
|
||||||
@ -104,6 +102,7 @@ require (
|
|||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.6 // indirect
|
github.com/rivo/uniseg v0.4.6 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
|
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// TODO: @AndrewChubatiuk, please provide a permalink for the original source code where these constants were extracted
|
||||||
epsillon = 1.0 / 128
|
epsillon = 1.0 / 128
|
||||||
gamma = 1 + 2*epsillon
|
gamma = 1 + 2*epsillon
|
||||||
gammaLn = math.Log(gamma)
|
gammaLn = math.Log(gamma)
|
||||||
@ -17,7 +18,16 @@ var (
|
|||||||
quantiles = []float64{0.5, 0.75, 0.9, 0.95, 0.99}
|
quantiles = []float64{0.5, 0.75, 0.9, 0.95, 0.99}
|
||||||
)
|
)
|
||||||
|
|
||||||
type label struct {
|
var quantilesStr = func() []string {
|
||||||
|
a := make([]string, len(quantiles))
|
||||||
|
for i, q := range quantiles {
|
||||||
|
a[i] = strconv.FormatFloat(q, 'g', 3, 64)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Label is a single label for Metric
|
||||||
|
type Label struct {
|
||||||
Name string
|
Name string
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
@ -25,8 +35,14 @@ type label struct {
|
|||||||
// Metric stores metrics extracted from sketches
|
// Metric stores metrics extracted from sketches
|
||||||
type Metric struct {
|
type Metric struct {
|
||||||
Name string
|
Name string
|
||||||
Labels []label
|
Labels []Label
|
||||||
Points []float64
|
Points []Point
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point stores a single point extracted from sketches
|
||||||
|
type Point struct {
|
||||||
|
Value float64
|
||||||
|
Timestamp int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// SketchPayload stores sketches extracted from /api/beta/sketches endpoint
|
// SketchPayload stores sketches extracted from /api/beta/sketches endpoint
|
||||||
@ -34,13 +50,15 @@ type Metric struct {
|
|||||||
// message SketchPayload {
|
// message SketchPayload {
|
||||||
// repeated Sketch sketches = 1
|
// repeated Sketch sketches = 1
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
|
// See https://github.com/DataDog/agent-payload/blob/38db68d9641c8a0bd2e1eac53b9d54793448850f/proto/metrics/agent_payload.proto#L90
|
||||||
type SketchPayload struct {
|
type SketchPayload struct {
|
||||||
Sketches []*Sketch
|
Sketches []*Sketch
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalProtobuf decodes byte array to SketchPayload struct
|
// UnmarshalProtobuf decodes src to SketchPayload struct
|
||||||
func (sp *SketchPayload) UnmarshalProtobuf(src []byte) (err error) {
|
func (sp *SketchPayload) UnmarshalProtobuf(src []byte) (err error) {
|
||||||
sp.Sketches = sp.Sketches[:0]
|
sp.Sketches = nil
|
||||||
var fc easyproto.FieldContext
|
var fc easyproto.FieldContext
|
||||||
for len(src) > 0 {
|
for len(src) > 0 {
|
||||||
src, err = fc.NextField(src)
|
src, err = fc.NextField(src)
|
||||||
@ -51,13 +69,13 @@ func (sp *SketchPayload) UnmarshalProtobuf(src []byte) (err error) {
|
|||||||
case 1:
|
case 1:
|
||||||
data, ok := fc.MessageData()
|
data, ok := fc.MessageData()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read SketchPayload sketches data")
|
return fmt.Errorf("cannot read Sketch data")
|
||||||
}
|
}
|
||||||
sp.Sketches = append(sp.Sketches, &Sketch{})
|
var s Sketch
|
||||||
s := sp.Sketches[len(sp.Sketches)-1]
|
|
||||||
if err := s.unmarshalProtobuf(data); err != nil {
|
if err := s.unmarshalProtobuf(data); err != nil {
|
||||||
return fmt.Errorf("cannot unmarshal sketch: %w", err)
|
return fmt.Errorf("cannot unmarshal Sketch: %w", err)
|
||||||
}
|
}
|
||||||
|
sp.Sketches = append(sp.Sketches, &s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -71,6 +89,8 @@ func (sp *SketchPayload) UnmarshalProtobuf(src []byte) (err error) {
|
|||||||
// repeated string tags = 4;
|
// repeated string tags = 4;
|
||||||
// repeated Dogsketch dogsketches = 7
|
// repeated Dogsketch dogsketches = 7
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
|
// See https://github.com/DataDog/agent-payload/blob/38db68d9641c8a0bd2e1eac53b9d54793448850f/proto/metrics/agent_payload.proto#L91
|
||||||
type Sketch struct {
|
type Sketch struct {
|
||||||
Metric string
|
Metric string
|
||||||
Host string
|
Host string
|
||||||
@ -78,12 +98,12 @@ type Sketch struct {
|
|||||||
Dogsketches []*Dogsketch
|
Dogsketches []*Dogsketch
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshalProtobuf decodes byte array to Sketch struct
|
// unmarshalProtobuf decodes src to Sketch struct
|
||||||
func (s *Sketch) unmarshalProtobuf(src []byte) (err error) {
|
func (s *Sketch) unmarshalProtobuf(src []byte) (err error) {
|
||||||
s.Metric = ""
|
s.Metric = ""
|
||||||
s.Host = ""
|
s.Host = ""
|
||||||
s.Tags = s.Tags[:0]
|
s.Tags = nil
|
||||||
s.Dogsketches = s.Dogsketches[:0]
|
s.Dogsketches = nil
|
||||||
|
|
||||||
var fc easyproto.FieldContext
|
var fc easyproto.FieldContext
|
||||||
for len(src) > 0 {
|
for len(src) > 0 {
|
||||||
@ -95,78 +115,87 @@ func (s *Sketch) unmarshalProtobuf(src []byte) (err error) {
|
|||||||
case 1:
|
case 1:
|
||||||
metric, ok := fc.String()
|
metric, ok := fc.String()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Sketch metric")
|
return fmt.Errorf("cannot read metric")
|
||||||
}
|
}
|
||||||
s.Metric = metric
|
s.Metric = metric
|
||||||
case 2:
|
case 2:
|
||||||
host, ok := fc.String()
|
host, ok := fc.String()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Sketch host")
|
return fmt.Errorf("cannot read host")
|
||||||
}
|
}
|
||||||
s.Host = host
|
s.Host = host
|
||||||
case 4:
|
case 4:
|
||||||
tag, ok := fc.String()
|
tag, ok := fc.String()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Sketch tag")
|
return fmt.Errorf("cannot read tag")
|
||||||
}
|
}
|
||||||
s.Tags = append(s.Tags, tag)
|
s.Tags = append(s.Tags, tag)
|
||||||
case 7:
|
case 7:
|
||||||
data, ok := fc.MessageData()
|
data, ok := fc.MessageData()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Sketch dogsketches data")
|
return fmt.Errorf("cannot read Dogsketch data")
|
||||||
}
|
}
|
||||||
s.Dogsketches = append(s.Dogsketches, &Dogsketch{})
|
var d Dogsketch
|
||||||
d := s.Dogsketches[len(s.Dogsketches)-1]
|
|
||||||
if err := d.unmarshalProtobuf(data); err != nil {
|
if err := d.unmarshalProtobuf(data); err != nil {
|
||||||
return fmt.Errorf("cannot unmarshal dogsketch: %w", err)
|
return fmt.Errorf("cannot unmarshal Dogsketch: %w", err)
|
||||||
}
|
}
|
||||||
|
s.Dogsketches = append(s.Dogsketches, &d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RowsCount calculates generated rows num from sketch
|
// RowsCount returns the number of samples s generates.
|
||||||
func (s *Sketch) RowsCount() int {
|
func (s *Sketch) RowsCount() int {
|
||||||
return (len(quantiles) + len(s.extractAggr())) * len(s.Dogsketches)
|
// The sketch contains len(quantiles) plus *_sum and *_count metrics
|
||||||
|
// per each Dogsketch in s.Dogsketches.
|
||||||
|
return (len(quantiles) + 2) * len(s.Dogsketches)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sketch) extractAggr() []*Metric {
|
// ToSummary generates Prometheus summary from the given s.
|
||||||
return []*Metric{
|
func (s *Sketch) ToSummary() []*Metric {
|
||||||
{
|
metrics := make([]*Metric, len(quantiles)+2)
|
||||||
Name: s.Metric + "_sum",
|
|
||||||
Labels: []label{},
|
|
||||||
Points: make([]float64, len(s.Dogsketches)),
|
|
||||||
}, {
|
|
||||||
Name: s.Metric + "_count",
|
|
||||||
Labels: []label{},
|
|
||||||
Points: make([]float64, len(s.Dogsketches)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToHistogram generates histogram metrics
|
|
||||||
func (s *Sketch) ToHistogram() []*Metric {
|
|
||||||
dogsketches := s.Dogsketches
|
dogsketches := s.Dogsketches
|
||||||
aggr := s.extractAggr()
|
|
||||||
metrics := make([]*Metric, len(quantiles))
|
sumPoints := make([]Point, len(dogsketches))
|
||||||
for q := range quantiles {
|
countPoints := make([]Point, len(dogsketches))
|
||||||
quantile := quantiles[q]
|
metrics[len(metrics)-2] = &Metric{
|
||||||
metrics[q] = &Metric{
|
Name: s.Metric + "_sum",
|
||||||
Name: s.Metric,
|
Points: sumPoints,
|
||||||
Labels: []label{{
|
}
|
||||||
Name: "quantile",
|
metrics[len(metrics)-1] = &Metric{
|
||||||
Value: strconv.FormatFloat(quantile, 'g', 3, 64),
|
Name: s.Metric + "_count",
|
||||||
}},
|
Points: countPoints,
|
||||||
Points: make([]float64, len(dogsketches)),
|
}
|
||||||
|
|
||||||
|
for i, q := range quantiles {
|
||||||
|
points := make([]Point, len(dogsketches))
|
||||||
|
for j, d := range dogsketches {
|
||||||
|
timestamp := d.Ts * 1000
|
||||||
|
points[j] = Point{
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Value: d.valueForQuantile(q),
|
||||||
|
}
|
||||||
|
sumPoints[j] = Point{
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Value: d.Sum,
|
||||||
|
}
|
||||||
|
countPoints[j] = Point{
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Value: float64(d.Cnt),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for d := range dogsketches {
|
metrics[i] = &Metric{
|
||||||
dogsketch := dogsketches[d]
|
Name: s.Metric,
|
||||||
aggr[0].Points[d] = dogsketch.Sum
|
Labels: []Label{{
|
||||||
aggr[1].Points[d] = float64(dogsketch.Cnt)
|
Name: "quantile",
|
||||||
metrics[q].Points[d] = dogsketch.pointForQuantile(quantile)
|
Value: quantilesStr[i],
|
||||||
|
}},
|
||||||
|
Points: points,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return append(metrics, aggr...)
|
|
||||||
|
return metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dogsketch proto struct
|
// Dogsketch proto struct
|
||||||
@ -180,6 +209,8 @@ func (s *Sketch) ToHistogram() []*Metric {
|
|||||||
// repeated sint32 k = 7;
|
// repeated sint32 k = 7;
|
||||||
// repeated uint32 n = 8;
|
// repeated uint32 n = 8;
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
|
// See https://github.com/DataDog/agent-payload/blob/38db68d9641c8a0bd2e1eac53b9d54793448850f/proto/metrics/agent_payload.proto#L104
|
||||||
type Dogsketch struct {
|
type Dogsketch struct {
|
||||||
Ts int64
|
Ts int64
|
||||||
Cnt int64
|
Cnt int64
|
||||||
@ -190,91 +221,102 @@ type Dogsketch struct {
|
|||||||
N []uint32
|
N []uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshalProtobuf decodes byte array to Dogsketch struct
|
// unmarshalProtobuf decodes src to Dogsketch struct
|
||||||
func (d *Dogsketch) unmarshalProtobuf(src []byte) (err error) {
|
func (d *Dogsketch) unmarshalProtobuf(src []byte) (err error) {
|
||||||
d.Ts = 0
|
d.Ts = 0
|
||||||
d.Cnt = 0
|
d.Cnt = 0
|
||||||
d.Min = 0.0
|
d.Min = 0.0
|
||||||
d.Max = 0.0
|
d.Max = 0.0
|
||||||
d.Sum = 0.0
|
d.Sum = 0.0
|
||||||
d.K = d.K[:0]
|
d.K = nil
|
||||||
d.N = d.N[:0]
|
d.N = nil
|
||||||
|
|
||||||
var fc easyproto.FieldContext
|
var fc easyproto.FieldContext
|
||||||
for len(src) > 0 {
|
for len(src) > 0 {
|
||||||
src, err = fc.NextField(src)
|
src, err = fc.NextField(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot read next field in Dogsketch message, %w", err)
|
return fmt.Errorf("cannot read next field in Dogsketch message: %w", err)
|
||||||
}
|
}
|
||||||
switch fc.FieldNum {
|
switch fc.FieldNum {
|
||||||
case 1:
|
case 1:
|
||||||
ts, ok := fc.Int64()
|
ts, ok := fc.Int64()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Dogsketch timestamp")
|
return fmt.Errorf("cannot read timestamp")
|
||||||
}
|
}
|
||||||
d.Ts = ts
|
d.Ts = ts
|
||||||
case 2:
|
case 2:
|
||||||
cnt, ok := fc.Int64()
|
cnt, ok := fc.Int64()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Dogsketch count")
|
return fmt.Errorf("cannot read count")
|
||||||
}
|
}
|
||||||
d.Cnt = cnt
|
d.Cnt = cnt
|
||||||
case 3:
|
case 3:
|
||||||
min, ok := fc.Double()
|
min, ok := fc.Double()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Dogsketch min")
|
return fmt.Errorf("cannot read min")
|
||||||
}
|
}
|
||||||
d.Min = min
|
d.Min = min
|
||||||
case 4:
|
case 4:
|
||||||
max, ok := fc.Double()
|
max, ok := fc.Double()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Dogsketch max")
|
return fmt.Errorf("cannot read max")
|
||||||
}
|
}
|
||||||
d.Max = max
|
d.Max = max
|
||||||
case 6:
|
case 6:
|
||||||
sum, ok := fc.Double()
|
sum, ok := fc.Double()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Dogsketch sum")
|
return fmt.Errorf("cannot read sum")
|
||||||
}
|
}
|
||||||
d.Sum = sum
|
d.Sum = sum
|
||||||
case 7:
|
case 7:
|
||||||
var ok bool
|
var ok bool
|
||||||
d.K, ok = fc.UnpackSint32s(d.K)
|
d.K, ok = fc.UnpackSint32s(d.K)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Dogsketch k")
|
return fmt.Errorf("cannot read k")
|
||||||
}
|
}
|
||||||
case 8:
|
case 8:
|
||||||
var ok bool
|
var ok bool
|
||||||
d.N, ok = fc.UnpackUint32s(d.N)
|
d.N, ok = fc.UnpackUint32s(d.N)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot read Dogsketch n")
|
return fmt.Errorf("cannot read n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dogsketch) pointForQuantile(quantile float64) float64 {
|
func (d *Dogsketch) valueForQuantile(q float64) float64 {
|
||||||
switch {
|
switch {
|
||||||
case d.Cnt == 0:
|
case d.Cnt == 0:
|
||||||
return 0
|
return 0
|
||||||
case quantile <= 0:
|
case q <= 0:
|
||||||
return d.Min
|
return d.Min
|
||||||
case quantile >= 1:
|
case q >= 1:
|
||||||
return d.Max
|
return d.Max
|
||||||
}
|
}
|
||||||
|
|
||||||
rank := quantile * float64(d.Cnt-1)
|
ns := d.N
|
||||||
nLen := len(d.N)
|
ks := d.K
|
||||||
for cnt, i := 0.0, 0; i < nLen; i++ {
|
if len(ns) != len(ks) {
|
||||||
cnt += float64(d.N[i])
|
// Avoid index out of range panic in the loop below.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
rank := q * float64(d.Cnt-1)
|
||||||
|
cnt := float64(0)
|
||||||
|
for i, n := range ns {
|
||||||
|
cnt += float64(n)
|
||||||
if cnt <= rank {
|
if cnt <= rank {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
weight := (cnt - rank) / float64(d.N[i])
|
weight := (cnt - rank) / float64(n)
|
||||||
vLow := f64(d.K[i])
|
vLow := f64(ks[i])
|
||||||
vHigh := vLow * gamma
|
vHigh := vLow * gamma
|
||||||
switch i {
|
switch i {
|
||||||
case nLen:
|
// TODO: I'm unsure this code is correct. i cannot equal len(ns) in this loop.
|
||||||
|
// @AndrewChubatiuk, please add a permalink to the original source code, which was used
|
||||||
|
// for writing this code, in the comments to this function.
|
||||||
|
case len(ns):
|
||||||
vHigh = d.Max
|
vHigh = d.Max
|
||||||
case 0:
|
case 0:
|
||||||
vLow = d.Min
|
vLow = d.Min
|
||||||
@ -288,6 +330,8 @@ func f64(k int32) float64 {
|
|||||||
switch {
|
switch {
|
||||||
case k < 0:
|
case k < 0:
|
||||||
return -f64(-k)
|
return -f64(-k)
|
||||||
|
// TODO: I'm unsure this logic is correct, since k can be smaller than math.MinInt16 and bigger than math.MaxInt16
|
||||||
|
// @AndrewChubatiuk, please add a permalink to the original source code, which was used for writing this code.
|
||||||
case k == math.MaxInt16 || k == math.MinInt16:
|
case k == math.MaxInt16 || k == math.MinInt16:
|
||||||
return math.Inf(int(k))
|
return math.Inf(int(k))
|
||||||
case k == 0:
|
case k == 0:
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
package datadogsketches
|
package datadogsketches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPointsForQuantile(t *testing.T) {
|
func TestPointsForQuantile(t *testing.T) {
|
||||||
f := func(d *Dogsketch, quantile float64, expected float64) {
|
f := func(d *Dogsketch, q float64, vExpected float64) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
point := d.pointForQuantile(quantile)
|
v := d.valueForQuantile(q)
|
||||||
assert.InDelta(t, point, expected, 0.5)
|
if math.Abs(v-vExpected) > 0.4 {
|
||||||
|
t.Fatalf("unexpected value; got %v; want %v", v, vExpected)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sketches := &Dogsketch{
|
sketches := &Dogsketch{
|
||||||
Min: 8.0,
|
Min: 8.0,
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
//
|
//
|
||||||
// callback shouldn't hold series after returning.
|
// callback shouldn't hold series after returning.
|
||||||
func Parse(r io.Reader, contentEncoding string, callback func(series []*datadogsketches.Sketch) error) error {
|
func Parse(r io.Reader, contentEncoding string, callback func(series []*datadogsketches.Sketch) error) error {
|
||||||
var err error
|
|
||||||
wcr := writeconcurrencylimiter.GetReader(r)
|
wcr := writeconcurrencylimiter.GetReader(r)
|
||||||
defer writeconcurrencylimiter.PutReader(wcr)
|
defer writeconcurrencylimiter.PutReader(wcr)
|
||||||
r = wcr
|
r = wcr
|
||||||
@ -50,18 +49,17 @@ func Parse(r io.Reader, contentEncoding string, callback func(series []*datadogs
|
|||||||
req := getRequest()
|
req := getRequest()
|
||||||
defer putRequest(req)
|
defer putRequest(req)
|
||||||
|
|
||||||
err = req.UnmarshalProtobuf(ctx.reqBuf.B)
|
if err := req.UnmarshalProtobuf(ctx.reqBuf.B); err != nil {
|
||||||
if err != nil {
|
|
||||||
unmarshalErrors.Inc()
|
unmarshalErrors.Inc()
|
||||||
return fmt.Errorf("cannot unmarshal DataDog request with size %d bytes: %w", len(ctx.reqBuf.B), err)
|
return fmt.Errorf("cannot unmarshal DataDog Sketches request with size %d bytes: %w", len(ctx.reqBuf.B), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rows := 0
|
rows := 0
|
||||||
sketches := req.Sketches
|
sketches := req.Sketches
|
||||||
for i := range sketches {
|
for _, sketch := range sketches {
|
||||||
rows += len(sketches[i].Dogsketches)
|
rows += sketch.RowsCount()
|
||||||
if *datadogutils.SanitizeMetricName {
|
if *datadogutils.SanitizeMetricName {
|
||||||
sketches[i].Metric = datadogutils.SanitizeName(sketches[i].Metric)
|
sketch.Metric = datadogutils.SanitizeName(sketch.Metric)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rowsRead.Add(rows)
|
rowsRead.Add(rows)
|
||||||
|
@ -40,6 +40,7 @@ func Parse(r io.Reader, contentEncoding string, callback func(series []datadogv1
|
|||||||
defer common.PutZlibReader(zlr)
|
defer common.PutZlibReader(zlr)
|
||||||
r = zlr
|
r = zlr
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := getPushCtx(r)
|
ctx := getPushCtx(r)
|
||||||
defer putPushCtx(ctx)
|
defer putPushCtx(ctx)
|
||||||
if err := ctx.Read(); err != nil {
|
if err := ctx.Read(); err != nil {
|
||||||
@ -47,10 +48,12 @@ func Parse(r io.Reader, contentEncoding string, callback func(series []datadogv1
|
|||||||
}
|
}
|
||||||
req := getRequest()
|
req := getRequest()
|
||||||
defer putRequest(req)
|
defer putRequest(req)
|
||||||
|
|
||||||
if err := req.Unmarshal(ctx.reqBuf.B); err != nil {
|
if err := req.Unmarshal(ctx.reqBuf.B); err != nil {
|
||||||
unmarshalErrors.Inc()
|
unmarshalErrors.Inc()
|
||||||
return fmt.Errorf("cannot unmarshal DataDog POST request with size %d bytes: %w", len(ctx.reqBuf.B), err)
|
return fmt.Errorf("cannot unmarshal DataDog POST request with size %d bytes: %w", len(ctx.reqBuf.B), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rows := 0
|
rows := 0
|
||||||
series := req.Series
|
series := req.Series
|
||||||
for i := range series {
|
for i := range series {
|
||||||
|
Loading…
Reference in New Issue
Block a user