Skip to content

Commit 434f9f3

Browse files
mx-psiKSerrania
andauthored
[datadogexporter] Add support for summary datatype (#3660)
* Add support for summary datatype * Apply suggestions from code review Co-authored-by: Kylian Serrania <[email protected]> Co-authored-by: Kylian Serrania <[email protected]>
1 parent 9126352 commit 434f9f3

File tree

7 files changed

+158
-0
lines changed

7 files changed

+158
-0
lines changed

exporter/datadogexporter/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,4 @@ There are a number of optional settings for configuring how to send your metrics
8686
|-|-|-|
8787
| `send_monotonic_counter` | Cumulative monotonic metrics are sent as deltas between successive measurements. Disable this flag to send get the raw, monotonically increasing value. | `true` |
8888
| `delta_ttl` | Maximum number of seconds values from cumulative monotonic metrics are kept in memory. | 3600 |
89+
| `report_quantiles` | Whether to report quantile values for summary type metrics. | `true` |

exporter/datadogexporter/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ type MetricsConfig struct {
6363
// Buckets states whether to report buckets from distribution metrics
6464
Buckets bool `mapstructure:"report_buckets"`
6565

66+
// Quantiles states whether to report quantiles from summary metrics.
67+
// By default, the minimum, maximum and average are reported.
68+
Quantiles bool `mapstructure:"report_quantiles"`
69+
6670
// SendMonotonic states whether to report cumulative monotonic metrics as counters
6771
// or gauges
6872
SendMonotonic bool `mapstructure:"send_monotonic_counter"`

exporter/datadogexporter/factory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func createDefaultConfig() config.Exporter {
6666
},
6767
SendMonotonic: true,
6868
DeltaTTL: 3600,
69+
Quantiles: true,
6970
ExporterConfig: ddconfig.MetricsExporterConfig{
7071
ResourceAttributesAsTags: false,
7172
},

exporter/datadogexporter/factory_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func TestCreateDefaultConfig(t *testing.T) {
5757
},
5858
DeltaTTL: 3600,
5959
SendMonotonic: true,
60+
Quantiles: true,
6061
},
6162

6263
Traces: ddconfig.TracesConfig{
@@ -121,6 +122,7 @@ func TestLoadConfig(t *testing.T) {
121122
},
122123
DeltaTTL: 3600,
123124
SendMonotonic: true,
125+
Quantiles: true,
124126
},
125127

126128
Traces: ddconfig.TracesConfig{
@@ -160,6 +162,7 @@ func TestLoadConfig(t *testing.T) {
160162
},
161163
SendMonotonic: true,
162164
DeltaTTL: 3600,
165+
Quantiles: true,
163166
},
164167

165168
Traces: ddconfig.TracesConfig{
@@ -241,6 +244,7 @@ func TestLoadConfigEnvVariables(t *testing.T) {
241244
Endpoint: "https://api.datadoghq.test",
242245
},
243246
SendMonotonic: true,
247+
Quantiles: false,
244248
DeltaTTL: 3600,
245249
},
246250

@@ -285,6 +289,7 @@ func TestLoadConfigEnvVariables(t *testing.T) {
285289
},
286290
SendMonotonic: true,
287291
DeltaTTL: 3600,
292+
Quantiles: true,
288293
},
289294

290295
Traces: ddconfig.TracesConfig{

exporter/datadogexporter/metrics_translator.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package datadogexporter
1717
import (
1818
"fmt"
1919
"sort"
20+
"strconv"
2021
"strings"
2122
"time"
2223

@@ -251,6 +252,52 @@ func mapHistogramMetrics(name string, slice pdata.HistogramDataPointSlice, bucke
251252
return ms
252253
}
253254

255+
// getQuantileTag returns the quantile tag for summary types.
256+
// Since the summary type is provided as a compatibility feature, we try to format the float number as close as possible to what
257+
// we do on the Datadog Agent Python OpenMetrics check, which, in turn, tries to
258+
// follow https://github.com/OpenObservability/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#considerations-canonical-numbers
259+
func getQuantileTag(quantile float64) string {
260+
// We handle 0 and 1 separately since they are special
261+
if quantile == 0 {
262+
// we do this differently on our check
263+
return "quantile:0"
264+
} else if quantile == 1.0 {
265+
// it needs to have a '.0' added at the end according to the spec
266+
return "quantile:1.0"
267+
}
268+
return fmt.Sprintf("quantile:%s", strconv.FormatFloat(quantile, 'g', -1, 64))
269+
}
270+
271+
// mapSummaryMetrics maps summary datapoints into Datadog metrics
272+
func mapSummaryMetrics(name string, slice pdata.SummaryDataPointSlice, quantiles bool, attrTags []string) []datadog.Metric {
273+
// Allocate assuming none are nil and no quantiles
274+
ms := make([]datadog.Metric, 0, 2*slice.Len())
275+
for i := 0; i < slice.Len(); i++ {
276+
p := slice.At(i)
277+
ts := uint64(p.Timestamp())
278+
tags := getTags(p.LabelsMap())
279+
tags = append(tags, attrTags...)
280+
281+
ms = append(ms,
282+
metrics.NewGauge(fmt.Sprintf("%s.count", name), ts, float64(p.Count()), tags),
283+
metrics.NewGauge(fmt.Sprintf("%s.sum", name), ts, p.Sum(), tags),
284+
)
285+
286+
if quantiles {
287+
fullName := fmt.Sprintf("%s.quantile", name)
288+
quantiles := p.QuantileValues()
289+
for i := 0; i < quantiles.Len(); i++ {
290+
q := quantiles.At(i)
291+
quantileTags := append(tags, getQuantileTag(q.Quantile()))
292+
ms = append(ms,
293+
metrics.NewGauge(fullName, ts, q.Value(), quantileTags),
294+
)
295+
}
296+
}
297+
}
298+
return ms
299+
}
300+
254301
// mapMetrics maps OTLP metrics into the DataDog format
255302
func mapMetrics(logger *zap.Logger, cfg config.MetricsConfig, prevPts *ttlmap.TTLMap, fallbackHost string, md pdata.Metrics, buildInfo component.BuildInfo) (series []datadog.Metric, droppedTimeSeries int) {
256303
pushTime := uint64(time.Now().UTC().UnixNano())
@@ -301,6 +348,8 @@ func mapMetrics(logger *zap.Logger, cfg config.MetricsConfig, prevPts *ttlmap.TT
301348
datapoints = mapIntHistogramMetrics(md.Name(), md.IntHistogram().DataPoints(), cfg.Buckets, attributeTags)
302349
case pdata.MetricDataTypeHistogram:
303350
datapoints = mapHistogramMetrics(md.Name(), md.Histogram().DataPoints(), cfg.Buckets, attributeTags)
351+
case pdata.MetricDataTypeSummary:
352+
datapoints = mapSummaryMetrics(md.Name(), md.Summary().DataPoints(), cfg.Quantiles, attributeTags)
304353
default: // pdata.MetricDataTypeNone or any other not supported type
305354
logger.Debug("Unknown or unsupported metric type", zap.String("metric name", md.Name()), zap.Any("data type", md.DataType()))
306355
continue

exporter/datadogexporter/metrics_translator_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,95 @@ func TestMapHistogramMetrics(t *testing.T) {
517517
)
518518
}
519519

520+
func TestQuantileTag(t *testing.T) {
521+
tests := []struct {
522+
quantile float64
523+
tag string
524+
}{
525+
{quantile: 0, tag: "quantile:0"},
526+
{quantile: 0.001, tag: "quantile:0.001"},
527+
{quantile: 0.9, tag: "quantile:0.9"},
528+
{quantile: 0.95, tag: "quantile:0.95"},
529+
{quantile: 0.99, tag: "quantile:0.99"},
530+
{quantile: 0.999, tag: "quantile:0.999"},
531+
{quantile: 1, tag: "quantile:1.0"},
532+
{quantile: 1e-10, tag: "quantile:1e-10"},
533+
}
534+
535+
for _, test := range tests {
536+
assert.Equal(t, test.tag, getQuantileTag(test.quantile))
537+
}
538+
}
539+
540+
func exampleSummaryDataPointSlice(ts pdata.Timestamp) pdata.SummaryDataPointSlice {
541+
slice := pdata.NewSummaryDataPointSlice()
542+
point := slice.AppendEmpty()
543+
point.SetCount(100)
544+
point.SetSum(10_000)
545+
qSlice := point.QuantileValues()
546+
547+
qMin := qSlice.AppendEmpty()
548+
qMin.SetQuantile(0.0)
549+
qMin.SetValue(0)
550+
551+
qMedian := qSlice.AppendEmpty()
552+
qMedian.SetQuantile(0.5)
553+
qMedian.SetValue(100)
554+
555+
q999 := qSlice.AppendEmpty()
556+
q999.SetQuantile(0.999)
557+
q999.SetValue(500)
558+
559+
qMax := qSlice.AppendEmpty()
560+
qMax.SetQuantile(1)
561+
qMax.SetValue(600)
562+
point.SetTimestamp(ts)
563+
return slice
564+
}
565+
566+
func TestMapSummaryMetrics(t *testing.T) {
567+
ts := pdata.TimestampFromTime(time.Now())
568+
slice := exampleSummaryDataPointSlice(ts)
569+
570+
noQuantiles := []datadog.Metric{
571+
metrics.NewGauge("summary.example.count", uint64(ts), 100, []string{}),
572+
metrics.NewGauge("summary.example.sum", uint64(ts), 10_000, []string{}),
573+
}
574+
quantiles := []datadog.Metric{
575+
metrics.NewGauge("summary.example.quantile", uint64(ts), 0, []string{"quantile:0"}),
576+
metrics.NewGauge("summary.example.quantile", uint64(ts), 100, []string{"quantile:0.5"}),
577+
metrics.NewGauge("summary.example.quantile", uint64(ts), 500, []string{"quantile:0.999"}),
578+
metrics.NewGauge("summary.example.quantile", uint64(ts), 600, []string{"quantile:1.0"}),
579+
}
580+
assert.ElementsMatch(t,
581+
mapSummaryMetrics("summary.example", slice, false, []string{}),
582+
noQuantiles,
583+
)
584+
assert.ElementsMatch(t,
585+
mapSummaryMetrics("summary.example", slice, true, []string{}),
586+
append(noQuantiles, quantiles...),
587+
)
588+
589+
noQuantilesAttr := []datadog.Metric{
590+
metrics.NewGauge("summary.example.count", uint64(ts), 100, []string{"attribute_tag:attribute_value"}),
591+
metrics.NewGauge("summary.example.sum", uint64(ts), 10_000, []string{"attribute_tag:attribute_value"}),
592+
}
593+
quantilesAttr := []datadog.Metric{
594+
metrics.NewGauge("summary.example.quantile", uint64(ts), 0, []string{"attribute_tag:attribute_value", "quantile:0"}),
595+
metrics.NewGauge("summary.example.quantile", uint64(ts), 100, []string{"attribute_tag:attribute_value", "quantile:0.5"}),
596+
metrics.NewGauge("summary.example.quantile", uint64(ts), 500, []string{"attribute_tag:attribute_value", "quantile:0.999"}),
597+
metrics.NewGauge("summary.example.quantile", uint64(ts), 600, []string{"attribute_tag:attribute_value", "quantile:1.0"}),
598+
}
599+
assert.ElementsMatch(t,
600+
mapSummaryMetrics("summary.example", slice, false, []string{"attribute_tag:attribute_value"}),
601+
noQuantilesAttr,
602+
)
603+
assert.ElementsMatch(t,
604+
mapSummaryMetrics("summary.example", slice, true, []string{"attribute_tag:attribute_value"}),
605+
append(noQuantilesAttr, quantilesAttr...),
606+
)
607+
}
608+
520609
func TestRunningMetrics(t *testing.T) {
521610
ms := pdata.NewMetrics()
522611
rms := ms.ResourceMetrics()
@@ -665,6 +754,12 @@ func createTestMetrics() pdata.Metrics {
665754
dpDouble.SetTimestamp(seconds(2))
666755
dpDouble.SetValue(4 + math.Pi)
667756

757+
// Summary
758+
met = metricsArray.AppendEmpty()
759+
met.SetName("summary")
760+
met.SetDataType(pdata.MetricDataTypeSummary)
761+
slice := exampleSummaryDataPointSlice(seconds(0))
762+
slice.CopyTo(met.Summary().DataPoints())
668763
return md
669764
}
670765

@@ -712,6 +807,8 @@ func TestMapMetrics(t *testing.T) {
712807
testGauge("int.histogram.count", 20),
713808
testGauge("double.histogram.sum", math.Phi),
714809
testGauge("double.histogram.count", 20),
810+
testGauge("summary.sum", 10_000),
811+
testGauge("summary.count", 100),
715812
testCount("int.cumulative.sum", 3),
716813
testCount("double.cumulative.sum", math.Pi),
717814
})

exporter/datadogexporter/testdata/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ exporters:
3636

3737
metrics:
3838
endpoint: https://api.datadoghq.test
39+
report_quantiles: false
3940

4041
traces:
4142
sample_rate: 1

0 commit comments

Comments
 (0)