diff --git a/exporter/datadogexporter/internal/metrics/consumer.go b/exporter/datadogexporter/internal/metrics/consumer.go new file mode 100644 index 000000000000..30ae7c313b26 --- /dev/null +++ b/exporter/datadogexporter/internal/metrics/consumer.go @@ -0,0 +1,156 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metrics" + +import ( + "context" + + "github.com/DataDog/datadog-agent/pkg/otlp/model/translator" + "github.com/DataDog/datadog-agent/pkg/quantile" + "github.com/DataDog/datadog-agent/pkg/trace/pb" + "github.com/DataDog/datadog-api-client-go/v2/api/datadog" + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "go.opentelemetry.io/collector/component" + + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metrics/sketches" +) + +var _ translator.Consumer = (*Consumer)(nil) +var _ translator.HostConsumer = (*Consumer)(nil) +var _ translator.TagsConsumer = (*Consumer)(nil) +var _ translator.APMStatsConsumer = (*Consumer)(nil) + +// Consumer implements translator.Consumer. It records consumed metrics, sketches and +// APM stats payloads. It provides them to the caller using the All method. +type Consumer struct { + ms []datadogV2.MetricSeries + sl sketches.SketchSeriesList + as []pb.ClientStatsPayload + seenHosts map[string]struct{} + seenTags map[string]struct{} +} + +// NewConsumer creates a new Datadog consumer. It implements translator.Consumer. +func NewConsumer() *Consumer { + return &Consumer{ + seenHosts: make(map[string]struct{}), + seenTags: make(map[string]struct{}), + } +} + +// toDataType maps translator datatypes to DatadogV2's datatypes. +func (c *Consumer) toDataType(dt translator.MetricDataType) (out datadogV2.MetricIntakeType) { + out = datadogV2.METRICINTAKETYPE_UNSPECIFIED + + switch dt { + case translator.Count: + out = datadogV2.METRICINTAKETYPE_COUNT + case translator.Gauge: + out = datadogV2.METRICINTAKETYPE_GAUGE + } + + return +} + +// runningMetrics gets the running metrics for the exporter. +func (c *Consumer) runningMetrics(timestamp uint64, buildInfo component.BuildInfo) (series []datadogV2.MetricSeries) { + for host := range c.seenHosts { + // Report the host as running + runningMetric := DefaultMetrics("metrics", host, timestamp, buildInfo) + series = append(series, runningMetric...) + } + + for tag := range c.seenTags { + runningMetrics := DefaultMetrics("metrics", "", timestamp, buildInfo) + for i := range runningMetrics { + runningMetrics[i].Tags = append(runningMetrics[i].Tags, tag) + } + series = append(series, runningMetrics...) + } + + return +} + +// All gets all metrics (consumed metrics and running metrics). +func (c *Consumer) All(timestamp uint64, buildInfo component.BuildInfo, tags []string) ([]datadogV2.MetricSeries, sketches.SketchSeriesList, []pb.ClientStatsPayload) { + series := c.ms + series = append(series, c.runningMetrics(timestamp, buildInfo)...) + if len(tags) == 0 { + return series, c.sl, c.as + } + for i := range series { + series[i].Tags = append(series[i].Tags, tags...) + } + for i := range c.sl { + c.sl[i].Tags = append(c.sl[i].Tags, tags...) + } + for i := range c.as { + c.as[i].Tags = append(c.as[i].Tags, tags...) + } + return series, c.sl, c.as +} + +// ConsumeAPMStats implements translator.APMStatsConsumer. +func (c *Consumer) ConsumeAPMStats(s pb.ClientStatsPayload) { + c.as = append(c.as, s) +} + +// ConsumeTimeSeries implements the translator.Consumer interface. +func (c *Consumer) ConsumeTimeSeries( + _ context.Context, + dims *translator.Dimensions, + typ translator.MetricDataType, + timestamp uint64, + value float64, +) { + dt := c.toDataType(typ) + met := NewMetric(dims.Name(), dt, timestamp, value, dims.Tags()) + met.SetResources([]datadogV2.MetricResource{ + { + Name: datadog.PtrString(dims.Host()), + Type: datadog.PtrString("host"), + }, + }) + c.ms = append(c.ms, met) +} + +// ConsumeSketch implements the translator.Consumer interface. +func (c *Consumer) ConsumeSketch( + _ context.Context, + dims *translator.Dimensions, + timestamp uint64, + sketch *quantile.Sketch, +) { + c.sl = append(c.sl, sketches.SketchSeries{ + Name: dims.Name(), + Tags: dims.Tags(), + Host: dims.Host(), + Interval: 1, + Points: []sketches.SketchPoint{{ + Ts: int64(timestamp / 1e9), + Sketch: sketch, + }}, + }) +} + +// ConsumeHost implements the translator.HostConsumer interface. +func (c *Consumer) ConsumeHost(host string) { + c.seenHosts[host] = struct{}{} +} + +// ConsumeTag implements the translator.TagsConsumer interface. +func (c *Consumer) ConsumeTag(tag string) { + c.seenTags[tag] = struct{}{} +} diff --git a/exporter/datadogexporter/internal/metrics/consumer_deprecated_test.go b/exporter/datadogexporter/internal/metrics/consumer_deprecated_test.go index ad4e5833e66d..5aaf4559332d 100644 --- a/exporter/datadogexporter/internal/metrics/consumer_deprecated_test.go +++ b/exporter/datadogexporter/internal/metrics/consumer_deprecated_test.go @@ -19,8 +19,6 @@ import ( "testing" "github.com/DataDog/datadog-agent/pkg/otlp/model/attributes" - "github.com/DataDog/datadog-agent/pkg/otlp/model/source" - "github.com/DataDog/datadog-agent/pkg/otlp/model/translator" "github.com/DataDog/datadog-agent/pkg/trace/pb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -32,23 +30,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/testutil" ) -type testProvider string - -func (t testProvider) Source(context.Context) (source.Source, error) { - return source.Source{Kind: source.HostnameKind, Identifier: string(t)}, nil -} - -func newTranslator(t *testing.T, logger *zap.Logger) *translator.Translator { - tr, err := translator.New(logger, - translator.WithHistogramMode(translator.HistogramModeDistributions), - translator.WithNumberMode(translator.NumberModeCumulativeToDelta), - translator.WithFallbackSourceProvider(testProvider("fallbackHostname")), - ) - require.NoError(t, err) - return tr -} - -func TestRunningMetrics(t *testing.T) { +func TestZorkianRunningMetrics(t *testing.T) { ms := pmetric.NewMetrics() rms := ms.ResourceMetrics() diff --git a/exporter/datadogexporter/internal/metrics/consumer_test.go b/exporter/datadogexporter/internal/metrics/consumer_test.go new file mode 100644 index 000000000000..d0f42118b8f6 --- /dev/null +++ b/exporter/datadogexporter/internal/metrics/consumer_test.go @@ -0,0 +1,150 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "context" + "testing" + + "github.com/DataDog/datadog-agent/pkg/otlp/model/attributes" + "github.com/DataDog/datadog-agent/pkg/otlp/model/source" + "github.com/DataDog/datadog-agent/pkg/otlp/model/translator" + "github.com/DataDog/datadog-agent/pkg/trace/pb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/pmetric" + conventions "go.opentelemetry.io/collector/semconv/v1.6.1" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/testutil" +) + +type testProvider string + +func (t testProvider) Source(context.Context) (source.Source, error) { + return source.Source{Kind: source.HostnameKind, Identifier: string(t)}, nil +} + +func newTranslator(t *testing.T, logger *zap.Logger) *translator.Translator { + tr, err := translator.New(logger, + translator.WithHistogramMode(translator.HistogramModeDistributions), + translator.WithNumberMode(translator.NumberModeCumulativeToDelta), + translator.WithFallbackSourceProvider(testProvider("fallbackHostname")), + ) + require.NoError(t, err) + return tr +} + +func TestRunningMetrics(t *testing.T) { + ms := pmetric.NewMetrics() + rms := ms.ResourceMetrics() + + rm := rms.AppendEmpty() + resAttrs := rm.Resource().Attributes() + resAttrs.PutStr(attributes.AttributeDatadogHostname, "resource-hostname-1") + + rm = rms.AppendEmpty() + resAttrs = rm.Resource().Attributes() + resAttrs.PutStr(attributes.AttributeDatadogHostname, "resource-hostname-1") + + rm = rms.AppendEmpty() + resAttrs = rm.Resource().Attributes() + resAttrs.PutStr(attributes.AttributeDatadogHostname, "resource-hostname-2") + + rms.AppendEmpty() + + logger, _ := zap.NewProduction() + tr := newTranslator(t, logger) + + ctx := context.Background() + consumer := NewConsumer() + assert.NoError(t, tr.MapMetrics(ctx, ms, consumer)) + + var runningHostnames []string + for _, metric := range consumer.runningMetrics(0, component.BuildInfo{}) { + for _, res := range metric.Resources { + runningHostnames = append(runningHostnames, *res.Name) + } + } + + assert.ElementsMatch(t, + runningHostnames, + []string{"fallbackHostname", "resource-hostname-1", "resource-hostname-2"}, + ) +} + +func TestTagsMetrics(t *testing.T) { + ms := pmetric.NewMetrics() + rms := ms.ResourceMetrics() + + rm := rms.AppendEmpty() + baseAttrs := testutil.NewAttributeMap(map[string]string{ + conventions.AttributeCloudProvider: conventions.AttributeCloudProviderAWS, + conventions.AttributeCloudPlatform: conventions.AttributeCloudPlatformAWSECS, + conventions.AttributeAWSECSTaskFamily: "example-task-family", + conventions.AttributeAWSECSTaskRevision: "example-task-revision", + conventions.AttributeAWSECSLaunchtype: conventions.AttributeAWSECSLaunchtypeFargate, + }) + baseAttrs.CopyTo(rm.Resource().Attributes()) + rm.Resource().Attributes().PutStr(conventions.AttributeAWSECSTaskARN, "task-arn-1") + + rm = rms.AppendEmpty() + baseAttrs.CopyTo(rm.Resource().Attributes()) + rm.Resource().Attributes().PutStr(conventions.AttributeAWSECSTaskARN, "task-arn-2") + + rm = rms.AppendEmpty() + baseAttrs.CopyTo(rm.Resource().Attributes()) + rm.Resource().Attributes().PutStr(conventions.AttributeAWSECSTaskARN, "task-arn-3") + + logger, _ := zap.NewProduction() + tr := newTranslator(t, logger) + + ctx := context.Background() + consumer := NewConsumer() + assert.NoError(t, tr.MapMetrics(ctx, ms, consumer)) + + runningMetrics := consumer.runningMetrics(0, component.BuildInfo{}) + var runningTags []string + var runningHostnames []string + for _, metric := range runningMetrics { + runningTags = append(runningTags, metric.Tags...) + for _, res := range metric.Resources { + runningHostnames = append(runningHostnames, *res.Name) + } + } + + assert.ElementsMatch(t, runningHostnames, []string{"", "", ""}) + assert.Len(t, runningMetrics, 3) + assert.ElementsMatch(t, runningTags, []string{"task_arn:task-arn-1", "task_arn:task-arn-2", "task_arn:task-arn-3"}) +} + +func TestConsumeAPMStats(t *testing.T) { + c := NewConsumer() + for _, sp := range testutil.StatsPayloads { + c.ConsumeAPMStats(sp) + } + require.Len(t, c.as, len(testutil.StatsPayloads)) + require.ElementsMatch(t, c.as, testutil.StatsPayloads) + _, _, out := c.All(0, component.BuildInfo{}, []string{}) + require.ElementsMatch(t, out, testutil.StatsPayloads) + _, _, out = c.All(0, component.BuildInfo{}, []string{"extra:key"}) + var copies []pb.ClientStatsPayload + for _, sp := range testutil.StatsPayloads { + sp.Tags = append(sp.Tags, "extra:key") + copies = append(copies, sp) + } + require.ElementsMatch(t, out, copies) +} diff --git a/exporter/datadogexporter/internal/metrics/system.go b/exporter/datadogexporter/internal/metrics/system.go new file mode 100644 index 000000000000..4752878089f3 --- /dev/null +++ b/exporter/datadogexporter/internal/metrics/system.go @@ -0,0 +1,132 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metrics" + +import ( + "strings" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" +) + +const ( + // divMebibytes specifies the number of bytes in a mebibyte. + divMebibytes = 1024 * 1024 + // divPercentage specifies the division necessary for converting fractions to percentages. + divPercentage = 0.01 + // otelNamespacePrefix specifies the namespace used for OpenTelemetry host metrics. + otelNamespacePrefix = "otel." +) + +// copySystemMetric copies the metric from src by giving it a new name. If div differs from 1, it scales all +// data points. +// +// Warning: this is not a deep copy. Only some fields are fully copied, others remain shared. This is intentional. +// Do not alter the returned metric (or the source one) after copying. +func copySystemMetric(src datadogV2.MetricSeries, name string, div float64) datadogV2.MetricSeries { + cp := src + cp.Metric = name + // No need to set cp.Interval if cp.Type is gauge. + cp.Type = datadogV2.METRICINTAKETYPE_GAUGE.Ptr() + if div == 0 || div == 1 || len(src.Points) == 0 { + // division by 0 or 1 should not have an impact + return cp + } + cp.Points = make([]datadogV2.MetricPoint, len(src.Points)) + for i, dp := range src.Points { + cp.Points[i].Timestamp = dp.Timestamp + if dp.Value != nil { + newdp := *dp.Value / div + cp.Points[i].Value = &newdp + } + } + return cp +} + +// extractSystemMetrics takes an OpenTelemetry metric m and extracts Datadog system metrics from it, +// if m is a valid system metric. The boolean argument reports whether any system metrics were extractd. +func extractSystemMetrics(m datadogV2.MetricSeries) []datadogV2.MetricSeries { + var series []datadogV2.MetricSeries + switch m.Metric { + case "system.cpu.load_average.1m": + series = append(series, copySystemMetric(m, "system.load.1", 1)) + case "system.cpu.load_average.5m": + series = append(series, copySystemMetric(m, "system.load.5", 1)) + case "system.cpu.load_average.15m": + series = append(series, copySystemMetric(m, "system.load.15", 1)) + case "system.cpu.utilization": + for _, tag := range m.Tags { + switch tag { + case "state:idle": + series = append(series, copySystemMetric(m, "system.cpu.idle", divPercentage)) + case "state:user": + series = append(series, copySystemMetric(m, "system.cpu.user", divPercentage)) + case "state:system": + series = append(series, copySystemMetric(m, "system.cpu.system", divPercentage)) + case "state:wait": + series = append(series, copySystemMetric(m, "system.cpu.iowait", divPercentage)) + case "state:steal": + series = append(series, copySystemMetric(m, "system.cpu.stolen", divPercentage)) + } + } + case "system.memory.usage": + series = append(series, copySystemMetric(m, "system.mem.total", divMebibytes)) + for _, tag := range m.Tags { + switch tag { + case "state:free", "state:cached", "state:buffered": + series = append(series, copySystemMetric(m, "system.mem.usable", divMebibytes)) + } + } + case "system.network.io": + for _, tag := range m.Tags { + switch tag { + case "direction:receive": + series = append(series, copySystemMetric(m, "system.net.bytes_rcvd", 1)) + case "direction:transmit": + series = append(series, copySystemMetric(m, "system.net.bytes_sent", 1)) + } + } + case "system.paging.usage": + for _, tag := range m.Tags { + switch tag { + case "state:free": + series = append(series, copySystemMetric(m, "system.swap.free", divMebibytes)) + case "state:used": + series = append(series, copySystemMetric(m, "system.swap.used", divMebibytes)) + } + } + case "system.filesystem.utilization": + series = append(series, copySystemMetric(m, "system.disk.in_use", 1)) + } + return series +} + +// PrepareSystemMetrics prepends system hosts metrics with the otel.* prefix to identify +// them as part of the Datadog OpenTelemetry Integration. It also extracts Datadog compatible +// system metrics and returns the full set of metrics to be used. +func PrepareSystemMetrics(ms []datadogV2.MetricSeries) []datadogV2.MetricSeries { + series := ms + for i, m := range ms { + if !strings.HasPrefix(m.Metric, "system.") && + !strings.HasPrefix(m.Metric, "process.") { + // not a system metric + continue + } + series = append(series, extractSystemMetrics(m)...) + // all existing system metrics need to be prepended + newname := otelNamespacePrefix + m.Metric + series[i].Metric = newname + } + return series +} diff --git a/exporter/datadogexporter/internal/metrics/system_deprecated.go b/exporter/datadogexporter/internal/metrics/system_deprecated.go index 1bdc723f1e05..3bd330be7d31 100644 --- a/exporter/datadogexporter/internal/metrics/system_deprecated.go +++ b/exporter/datadogexporter/internal/metrics/system_deprecated.go @@ -47,13 +47,6 @@ func copyZorkianSystemMetric(src zorkian.Metric, name string, div float64) zorki return cp } -const ( - // divMebibytes specifies the number of bytes in a mebibyte. - divMebibytes = 1024 * 1024 - // divPercentage specifies the division necessary for converting fractions to percentages. - divPercentage = 0.01 -) - // extractZorkianSystemMetric takes an OpenTelemetry metric m and extracts Datadog system metrics from it, // if m is a valid system metric. The boolean argument reports whether any system metrics were extractd. func extractZorkianSystemMetric(m zorkian.Metric) []zorkian.Metric { @@ -112,9 +105,6 @@ func extractZorkianSystemMetric(m zorkian.Metric) []zorkian.Metric { return series } -// otelNamespacePrefix specifies the namespace used for OpenTelemetry host metrics. -const otelNamespacePrefix = "otel." - // PrepareZorkianSystemMetrics prepends system hosts metrics with the otel.* prefix to identify // them as part of the Datadog OpenTelemetry Integration. It also extracts Datadog compatible // system metrics and returns the full set of metrics to be used. diff --git a/exporter/datadogexporter/internal/metrics/system_test.go b/exporter/datadogexporter/internal/metrics/system_test.go new file mode 100644 index 000000000000..8d045f710de5 --- /dev/null +++ b/exporter/datadogexporter/internal/metrics/system_test.go @@ -0,0 +1,664 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metrics" + +import ( + "fmt" + "math" + "testing" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadog" + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "github.com/stretchr/testify/require" +) + +func TestCopyMetric(t *testing.T) { + sptr := func(s string) *string { return &s } + dp := func(a int64, b float64) datadogV2.MetricPoint { + return datadogV2.MetricPoint{ + Timestamp: datadog.PtrInt64(a), + Value: datadog.PtrFloat64(b), + } + } + + var unspecified = datadogV2.METRICINTAKETYPE_UNSPECIFIED + var gauge = datadogV2.METRICINTAKETYPE_GAUGE + + t.Run("renaming", func(t *testing.T) { + require.EqualValues(t, copySystemMetric(datadogV2.MetricSeries{ + Metric: "oldname", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + Type: &unspecified, + Resources: []datadogV2.MetricResource{{Name: sptr("oldhost"), Type: sptr("host")}}, + Tags: []string{"x", "y", "z"}, + Unit: sptr("oldunit"), + }, "newname", 1), datadogV2.MetricSeries{ + Metric: "newname", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + Type: &gauge, + Resources: []datadogV2.MetricResource{{Name: sptr("oldhost"), Type: sptr("host")}}, + Tags: []string{"x", "y", "z"}, + Unit: sptr("oldunit"), + }) + + require.EqualValues(t, copySystemMetric(datadogV2.MetricSeries{ + Metric: "oldname", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + Type: &unspecified, + Resources: []datadogV2.MetricResource{{Name: sptr("oldhost"), Type: sptr("host")}}, + Tags: []string{"x", "y", "z"}, + Unit: sptr("oldunit"), + }, "", 1), datadogV2.MetricSeries{ + Metric: "", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + Type: &gauge, + Resources: []datadogV2.MetricResource{{Name: sptr("oldhost"), Type: sptr("host")}}, + Tags: []string{"x", "y", "z"}, + Unit: sptr("oldunit"), + }) + + require.EqualValues(t, copySystemMetric(datadogV2.MetricSeries{ + Metric: "", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + Type: &unspecified, + Resources: []datadogV2.MetricResource{{Name: sptr("oldhost"), Type: sptr("host")}}, + Tags: []string{"x", "y", "z"}, + Unit: sptr("oldunit"), + }, "", 1), datadogV2.MetricSeries{ + Metric: "", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + Type: &gauge, + Resources: []datadogV2.MetricResource{{Name: sptr("oldhost"), Type: sptr("host")}}, + Tags: []string{"x", "y", "z"}, + Unit: sptr("oldunit"), + }) + }) + + t.Run("division", func(t *testing.T) { + for _, tt := range []struct { + in, out []datadogV2.MetricPoint + div float64 + }{ + { + in: []datadogV2.MetricPoint{dp(0, 0), dp(1, 20)}, + div: 0, + out: []datadogV2.MetricPoint{dp(0, 0), dp(1, 20)}, + }, + { + in: []datadogV2.MetricPoint{dp(0, 0), dp(1, 20)}, + div: 1, + out: []datadogV2.MetricPoint{dp(0, 0), dp(1, 20)}, + }, + { + in: []datadogV2.MetricPoint{dp(1, 0), {}}, + div: 2, + out: []datadogV2.MetricPoint{dp(1, 0), {}}, + }, + { + in: []datadogV2.MetricPoint{dp(0, 0), dp(1, 20)}, + div: 10, + out: []datadogV2.MetricPoint{dp(0, 0), dp(1, 2)}, + }, + { + in: []datadogV2.MetricPoint{dp(1, 0), dp(55, math.MaxFloat64)}, + div: 1024 * 1024.5, + out: []datadogV2.MetricPoint{dp(1, 0), dp(55, 1.713577063947272e+302)}, + }, + { + in: []datadogV2.MetricPoint{dp(1, 0), dp(55, 20)}, + div: math.MaxFloat64, + out: []datadogV2.MetricPoint{dp(1, 0), dp(55, 1.1125369292536009e-307)}, + }, + } { + t.Run(fmt.Sprintf("%.0f", tt.div), func(t *testing.T) { + require.EqualValues(t, + copySystemMetric(datadogV2.MetricSeries{Points: tt.in}, "", tt.div), + datadogV2.MetricSeries{Metric: "", Type: &gauge, Points: tt.out}, + ) + }) + } + }) +} + +func TestExtractSystemMetrics(t *testing.T) { + dp := func(a int64, b float64) datadogV2.MetricPoint { + return datadogV2.MetricPoint{ + Timestamp: datadog.PtrInt64(a), + Value: datadog.PtrFloat64(b), + } + } + + var gauge = datadogV2.METRICINTAKETYPE_GAUGE + + for _, tt := range []struct { + in datadogV2.MetricSeries + out []datadogV2.MetricSeries + }{ + { + in: datadogV2.MetricSeries{ + Metric: "system.cpu.load_average.1m", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.load.1", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.cpu.load_average.5m", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.load.5", + Points: []datadogV2.MetricPoint{dp(1, 2), dp(3, 4)}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.cpu.load_average.15m", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.load.15", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:idle"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.cpu.idle", + Points: []datadogV2.MetricPoint{dp(2, 200), dp(3, 400)}, + Tags: []string{"state:idle"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:user"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.cpu.user", + Points: []datadogV2.MetricPoint{dp(2, 200), dp(3, 400)}, + Tags: []string{"state:user"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:system"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.cpu.system", + Points: []datadogV2.MetricPoint{dp(2, 200), dp(3, 400)}, + Tags: []string{"state:system"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:wait"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.cpu.iowait", + Points: []datadogV2.MetricPoint{dp(2, 200), dp(3, 400)}, + Tags: []string{"state:wait"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:steal"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.cpu.stolen", + Points: []datadogV2.MetricPoint{dp(2, 200), dp(3, 400)}, + Tags: []string{"state:steal"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.memory.usage", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:other"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.mem.total", + Points: []datadogV2.MetricPoint{dp(2, 1.9073486328125e-06), dp(3, 3.814697265625e-06)}, + Tags: []string{"state:other"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.memory.usage", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:free"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.mem.total", + Points: []datadogV2.MetricPoint{dp(2, 1.9073486328125e-06), dp(3, 3.814697265625e-06)}, + Tags: []string{"state:free"}, + Type: &gauge, + }, { + Metric: "system.mem.usable", + Points: []datadogV2.MetricPoint{dp(2, 1.9073486328125e-06), dp(3, 3.814697265625e-06)}, + Tags: []string{"state:free"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.memory.usage", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:cached"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.mem.total", + Points: []datadogV2.MetricPoint{dp(2, 1.9073486328125e-06), dp(3, 3.814697265625e-06)}, + Tags: []string{"state:cached"}, + Type: &gauge, + }, { + Metric: "system.mem.usable", + Points: []datadogV2.MetricPoint{dp(2, 1.9073486328125e-06), dp(3, 3.814697265625e-06)}, + Tags: []string{"state:cached"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.memory.usage", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:buffered"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.mem.total", + Points: []datadogV2.MetricPoint{dp(2, 1.9073486328125e-06), dp(3, 3.814697265625e-06)}, + Tags: []string{"state:buffered"}, + Type: &gauge, + }, { + Metric: "system.mem.usable", + Points: []datadogV2.MetricPoint{dp(2, 1.9073486328125e-06), dp(3, 3.814697265625e-06)}, + Tags: []string{"state:buffered"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.network.io", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"direction:receive"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.net.bytes_rcvd", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"direction:receive"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.network.io", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"direction:transmit"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.net.bytes_sent", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"direction:transmit"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.paging.usage", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:free"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.swap.free", + Points: []datadogV2.MetricPoint{dp(2, 1.9073486328125e-06), dp(3, 3.814697265625e-06)}, + Tags: []string{"state:free"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.paging.usage", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Tags: []string{"state:used"}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.swap.used", + Points: []datadogV2.MetricPoint{dp(2, 1.9073486328125e-06), dp(3, 3.814697265625e-06)}, + Tags: []string{"state:used"}, + Type: &gauge, + }}, + }, + { + in: datadogV2.MetricSeries{ + Metric: "system.filesystem.utilization", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + }, + out: []datadogV2.MetricSeries{{ + Metric: "system.disk.in_use", + Points: []datadogV2.MetricPoint{dp(2, 2), dp(3, 4)}, + Type: &gauge, + }}, + }, + } { + t.Run("", func(t *testing.T) { + out := extractSystemMetrics(tt.in) + require.EqualValues(t, tt.out, out, fmt.Sprintf("%s[%#v]", tt.in.Metric, tt.in.Tags)) + }) + } +} + +func TestPrepareSystemMetrics(t *testing.T) { + var gauge = datadogV2.METRICINTAKETYPE_GAUGE + + m := func(name string, tags []string, points ...float64) datadogV2.MetricSeries { + met := datadogV2.MetricSeries{ + Metric: name, + Tags: tags, + } + if len(points)%2 != 0 { + t.Fatal("Number of data point arguments passed to function must be even.") + } + met.Points = make([]datadogV2.MetricPoint, 0, len(points)/2) + for i := 0; i < len(points); i += 2 { + ts := int64(points[i]) + val := points[i+1] + met.Points = append(met.Points, datadogV2.MetricPoint{Timestamp: datadog.PtrInt64(ts), Value: datadog.PtrFloat64(val)}) + } + return met + } + + dp := func(a int64, b float64) datadogV2.MetricPoint { + return datadogV2.MetricPoint{ + Timestamp: datadog.PtrInt64(a), + Value: datadog.PtrFloat64(b), + } + } + + require.EqualValues(t, PrepareSystemMetrics([]datadogV2.MetricSeries{ + m("system.metric.1", nil, 0.1, 0.2), + m("system.metric.2", nil, 0.3, 0.4), + m("process.metric.1", nil, 0.5, 0.6), + m("process.metric.2", nil, 0.7, 0.8), + m("system.cpu.load_average.1m", nil, 1, 2), + m("system.cpu.load_average.5m", nil, 3, 4), + m("system.cpu.load_average.15m", nil, 5, 6), + m("system.cpu.utilization", []string{"state:idle"}, 0.15, 0.17), + m("system.cpu.utilization", []string{"state:user"}, 0.18, 0.19), + m("system.cpu.utilization", []string{"state:system"}, 0.20, 0.21), + m("system.cpu.utilization", []string{"state:wait"}, 0.22, 0.23), + m("system.cpu.utilization", []string{"state:steal"}, 0.24, 0.25), + m("system.cpu.utilization", []string{"state:other"}, 0.26, 0.27), + m("system.memory.usage", nil, 1, 0.30), + m("system.memory.usage", []string{"state:other"}, 1, 1.35), + m("system.memory.usage", []string{"state:free"}, 1, 1.30), + m("system.memory.usage", []string{"state:cached"}, 1, 1.35), + m("system.memory.usage", []string{"state:buffered"}, 1, 1.37, 2, 2.22), + m("system.network.io", []string{"direction:receive"}, 1, 2.37, 2, 3.22), + m("system.network.io", []string{"direction:transmit"}, 1, 4.37, 2, 5.22), + m("system.paging.usage", []string{"state:free"}, 1, 4.37, 2, 5.22), + m("system.paging.usage", []string{"state:used"}, 1, 4.3, 2, 8.22), + m("system.filesystem.utilization", nil, 1, 4.3, 2, 5.5, 3, 12.1), + }), []datadogV2.MetricSeries{ + { + Metric: "otel.system.metric.1", + Points: []datadogV2.MetricPoint{dp(0, 0.2)}, + }, + { + Metric: "otel.system.metric.2", + Points: []datadogV2.MetricPoint{dp(0, 0.4)}, + }, + { + Metric: "otel.process.metric.1", + Points: []datadogV2.MetricPoint{dp(0, 0.6)}, + }, + { + Metric: "otel.process.metric.2", + Points: []datadogV2.MetricPoint{dp(0, 0.8)}, + }, + { + Metric: "otel.system.cpu.load_average.1m", + Points: []datadogV2.MetricPoint{dp(1, 2)}, + }, + { + Metric: "otel.system.cpu.load_average.5m", + Points: []datadogV2.MetricPoint{dp(3, 4)}, + }, + { + Metric: "otel.system.cpu.load_average.15m", + Points: []datadogV2.MetricPoint{dp(5, 6)}, + }, + { + Metric: "otel.system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(0, 0.17)}, + Tags: []string{"state:idle"}, + }, + { + Metric: "otel.system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(0, 0.19)}, + Tags: []string{"state:user"}, + }, + { + Metric: "otel.system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(0, 0.21)}, + Tags: []string{"state:system"}, + }, + { + Metric: "otel.system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(0, 0.23)}, + Tags: []string{"state:wait"}}, + { + Metric: "otel.system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(0, 0.25)}, + Tags: []string{"state:steal"}, + }, + { + Metric: "otel.system.cpu.utilization", + Points: []datadogV2.MetricPoint{dp(0, 0.27)}, + Tags: []string{"state:other"}, + }, + { + Metric: "otel.system.memory.usage", + Points: []datadogV2.MetricPoint{dp(1, 0.3)}, + }, + { + Metric: "otel.system.memory.usage", + Points: []datadogV2.MetricPoint{dp(1, 1.35)}, + Tags: []string{"state:other"}, + }, + { + Metric: "otel.system.memory.usage", + Points: []datadogV2.MetricPoint{dp(1, 1.3)}, + Tags: []string{"state:free"}, + }, + { + Metric: "otel.system.memory.usage", + Points: []datadogV2.MetricPoint{dp(1, 1.35)}, + Tags: []string{"state:cached"}, + }, + { + Metric: "otel.system.memory.usage", + Points: []datadogV2.MetricPoint{dp(1, 1.37), dp(2, 2.22)}, + Tags: []string{"state:buffered"}, + }, + { + Metric: "otel.system.network.io", + Points: []datadogV2.MetricPoint{dp(1, 2.37), dp(2, 3.22)}, + Tags: []string{"direction:receive"}, + }, + { + Metric: "otel.system.network.io", + Points: []datadogV2.MetricPoint{dp(1, 4.37), dp(2, 5.22)}, + Tags: []string{"direction:transmit"}, + }, + { + Metric: "otel.system.paging.usage", + Points: []datadogV2.MetricPoint{dp(1, 4.37), dp(2, 5.22)}, + Tags: []string{"state:free"}, + }, + { + Metric: "otel.system.paging.usage", + Points: []datadogV2.MetricPoint{dp(1, 4.3), dp(2, 8.22)}, + Tags: []string{"state:used"}, + }, + { + Metric: "otel.system.filesystem.utilization", + Points: []datadogV2.MetricPoint{dp(1, 4.3), dp(2, 5.5), dp(3, 12.1)}, + }, + { + Metric: "system.load.1", + Points: []datadogV2.MetricPoint{dp(1, 2)}, + Type: &gauge, + }, + { + Metric: "system.load.5", + Points: []datadogV2.MetricPoint{dp(3, 4)}, + Type: &gauge, + }, + { + Metric: "system.load.15", + Points: []datadogV2.MetricPoint{dp(5, 6)}, + Type: &gauge, + }, + { + Metric: "system.cpu.idle", + Points: []datadogV2.MetricPoint{dp(0, 17)}, + Type: &gauge, + Tags: []string{"state:idle"}, + }, + { + Metric: "system.cpu.user", + Points: []datadogV2.MetricPoint{dp(0, 19)}, + Type: &gauge, + Tags: []string{"state:user"}, + }, + { + Metric: "system.cpu.system", + Points: []datadogV2.MetricPoint{dp(0, 21)}, + Type: &gauge, + Tags: []string{"state:system"}, + }, + { + Metric: "system.cpu.iowait", + Points: []datadogV2.MetricPoint{dp(0, 23)}, + Type: &gauge, + Tags: []string{"state:wait"}, + }, + { + Metric: "system.cpu.stolen", + Points: []datadogV2.MetricPoint{dp(0, 25)}, + Type: &gauge, + Tags: []string{"state:steal"}, + }, + { + Metric: "system.mem.total", + Points: []datadogV2.MetricPoint{dp(1, 2.86102294921875e-07)}, + Type: &gauge, + }, + { + Metric: "system.mem.total", + Points: []datadogV2.MetricPoint{dp(1, 1.2874603271484376e-06)}, + Type: &gauge, + Tags: []string{"state:other"}, + }, + { + Metric: "system.mem.total", + Points: []datadogV2.MetricPoint{dp(1, 1.239776611328125e-06)}, + Type: &gauge, + Tags: []string{"state:free"}, + }, + { + Metric: "system.mem.usable", + Points: []datadogV2.MetricPoint{dp(1, 1.239776611328125e-06)}, + Type: &gauge, + Tags: []string{"state:free"}, + }, + { + Metric: "system.mem.total", + Points: []datadogV2.MetricPoint{dp(1, 1.2874603271484376e-06)}, + Type: &gauge, + Tags: []string{"state:cached"}, + }, + { + Metric: "system.mem.usable", + Points: []datadogV2.MetricPoint{dp(1, 1.2874603271484376e-06)}, + Type: &gauge, + Tags: []string{"state:cached"}, + }, + { + Metric: "system.mem.total", + Points: []datadogV2.MetricPoint{dp(1, 1.3065338134765626e-06), dp(2, 2.117156982421875e-06)}, + Type: &gauge, + Tags: []string{"state:buffered"}, + }, + { + Metric: "system.mem.usable", + Points: []datadogV2.MetricPoint{dp(1, 1.3065338134765626e-06), dp(2, 2.117156982421875e-06)}, + Type: &gauge, + Tags: []string{"state:buffered"}, + }, + { + Metric: "system.net.bytes_rcvd", + Points: []datadogV2.MetricPoint{dp(1, 2.37), dp(2, 3.22)}, + Type: &gauge, + Tags: []string{"direction:receive"}, + }, + { + Metric: "system.net.bytes_sent", + Points: []datadogV2.MetricPoint{dp(1, 4.37), dp(2, 5.22)}, + Type: &gauge, + Tags: []string{"direction:transmit"}, + }, + { + Metric: "system.swap.free", + Points: []datadogV2.MetricPoint{dp(1, 4.37/1024/1024), dp(2, 5.22/1024/1024)}, + Type: &gauge, + Tags: []string{"state:free"}, + }, + { + Metric: "system.swap.used", + Points: []datadogV2.MetricPoint{dp(1, 4.3/1024/1024), dp(2, 8.22/1024/1024)}, + Type: &gauge, + Tags: []string{"state:used"}, + }, + { + Metric: "system.disk.in_use", + Points: []datadogV2.MetricPoint{dp(1, 4.3), dp(2, 5.5), dp(3, 12.1)}, + Type: &gauge, + }, + }) +} diff --git a/exporter/datadogexporter/internal/metrics/utils.go b/exporter/datadogexporter/internal/metrics/utils.go new file mode 100644 index 000000000000..52cf5b0ee7c7 --- /dev/null +++ b/exporter/datadogexporter/internal/metrics/utils.go @@ -0,0 +1,86 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metrics" + +import ( + "fmt" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadog" + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "go.opentelemetry.io/collector/component" +) + +// newMetricSeries creates a new Datadog metric series given a name, a Unix nanoseconds timestamp +// a value and a slice of tags +func newMetricSeries(name string, ts uint64, value float64, tags []string) datadogV2.MetricSeries { + // Transform UnixNano timestamp into Unix timestamp + // 1 second = 1e9 ns + timestamp := int64(ts / 1e9) + + metric := datadogV2.MetricSeries{ + Metric: name, + Points: []datadogV2.MetricPoint{ + { + Timestamp: datadog.PtrInt64(timestamp), + Value: datadog.PtrFloat64(value), + }, + }, + Tags: tags, + } + return metric +} + +// NewMetric creates a new DatadogV2 metric given a name, a type, a Unix nanoseconds timestamp +// a value and a slice of tags +func NewMetric(name string, dt datadogV2.MetricIntakeType, ts uint64, value float64, tags []string) datadogV2.MetricSeries { + metric := newMetricSeries(name, ts, value, tags) + metric.SetType(dt) + return metric +} + +// NewGauge creates a new DatadogV2 Gauge metric given a name, a Unix nanoseconds timestamp +// a value and a slice of tags +func NewGauge(name string, ts uint64, value float64, tags []string) datadogV2.MetricSeries { + return NewMetric(name, datadogV2.METRICINTAKETYPE_GAUGE, ts, value, tags) +} + +// NewCount creates a new DatadogV2 count metric given a name, a Unix nanoseconds timestamp +// a value and a slice of tags +func NewCount(name string, ts uint64, value float64, tags []string) datadogV2.MetricSeries { + return NewMetric(name, datadogV2.METRICINTAKETYPE_COUNT, ts, value, tags) +} + +// DefaultMetrics creates built-in metrics to report that an exporter is running +func DefaultMetrics(exporterType string, hostname string, timestamp uint64, buildInfo component.BuildInfo) []datadogV2.MetricSeries { + var tags []string + if buildInfo.Version != "" { + tags = append(tags, "version:"+buildInfo.Version) + } + if buildInfo.Command != "" { + tags = append(tags, "command:"+buildInfo.Command) + } + metrics := []datadogV2.MetricSeries{ + NewGauge(fmt.Sprintf("otel.datadog_exporter.%s.running", exporterType), timestamp, 1.0, tags), + } + for i := range metrics { + metrics[i].SetResources([]datadogV2.MetricResource{ + { + Name: datadog.PtrString(hostname), + Type: datadog.PtrString("host"), + }, + }) + } + return metrics +} diff --git a/exporter/datadogexporter/internal/metrics/utils_test.go b/exporter/datadogexporter/internal/metrics/utils_test.go new file mode 100644 index 000000000000..1c7e062e400e --- /dev/null +++ b/exporter/datadogexporter/internal/metrics/utils_test.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "testing" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component" +) + +func TestNewMetricSeries(t *testing.T) { + name := "test.metric" + ts := uint64(1e9) + value := 2.0 + tags := []string{"tag:value"} + + metric := newMetricSeries(name, ts, value, tags) + + assert.Equal(t, "test.metric", metric.Metric) + // Assert timestamp conversion from uint64 ns to int64 s + assert.Equal(t, int64(1), *metric.Points[0].Timestamp) + // Assert value + assert.Equal(t, 2.0, *metric.Points[0].Value) + // Assert tags + assert.Equal(t, []string{"tag:value"}, metric.Tags) +} + +func TestNewType(t *testing.T) { + name := "test.metric" + ts := uint64(1e9) + value := 2.0 + tags := []string{"tag:value"} + + gauge := NewGauge(name, ts, value, tags) + assert.Equal(t, gauge.GetType(), datadogV2.METRICINTAKETYPE_GAUGE) + + count := NewCount(name, ts, value, tags) + assert.Equal(t, count.GetType(), datadogV2.METRICINTAKETYPE_COUNT) +} + +func TestDefaultMetrics(t *testing.T) { + buildInfo := component.BuildInfo{ + Version: "1.0", + Command: "otelcontribcol", + } + + ms := DefaultMetrics("metrics", "test-host", uint64(2e9), buildInfo) + + assert.Equal(t, "otel.datadog_exporter.metrics.running", ms[0].Metric) + // Assert metrics list length (should be 1) + assert.Equal(t, 1, len(ms)) + // Assert timestamp + assert.Equal(t, int64(2), *ms[0].Points[0].Timestamp) + // Assert value (should always be 1.0) + assert.Equal(t, 1.0, *ms[0].Points[0].Value) + // Assert hostname tag is set + assert.Equal(t, "test-host", *ms[0].Resources[0].Name) + // Assert no other tags are set + assert.ElementsMatch(t, []string{"version:1.0", "command:otelcontribcol"}, ms[0].Tags) +}