From 175c7871a59c190131a00dcd7fe1f931247c68fa Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Mon, 20 Jun 2022 11:25:27 +0200 Subject: [PATCH 1/6] [pkg/otlp/model] Backport changes from Datadog exporter --- pkg/otlp/model/attributes/attributes.go | 17 ----- .../attributes/{hostname.go => source.go} | 46 ++++++++++-- .../{hostname_test.go => source_test.go} | 75 ++++++++++++------- pkg/otlp/model/source/source_provider.go | 51 +++++++++++++ pkg/otlp/model/translator/config.go | 14 ++-- .../model/translator/hostname_provider.go | 31 -------- .../model/translator/metrics_translator.go | 29 ++++--- .../translator/metrics_translator_test.go | 14 +++- 8 files changed, 176 insertions(+), 101 deletions(-) rename pkg/otlp/model/attributes/{hostname.go => source.go} (80%) rename pkg/otlp/model/attributes/{hostname_test.go => source_test.go} (76%) create mode 100644 pkg/otlp/model/source/source_provider.go delete mode 100644 pkg/otlp/model/translator/hostname_provider.go diff --git a/pkg/otlp/model/attributes/attributes.go b/pkg/otlp/model/attributes/attributes.go index aaa249b9877042..87685d5d734a98 100644 --- a/pkg/otlp/model/attributes/attributes.go +++ b/pkg/otlp/model/attributes/attributes.go @@ -92,10 +92,6 @@ var ( conventions.AttributeAWSECSContainerARN, } - runningTagsAttributes = []string{ - conventions.AttributeAWSECSTaskARN, - } - // Kubernetes mappings defines the mapping between Kubernetes conventions (both general and Datadog specific) // and Datadog Agent conventions. The Datadog Agent conventions can be found at // https://github.com/DataDog/datadog-agent/blob/e081bed/pkg/tagger/collectors/const.go and @@ -176,19 +172,6 @@ func OriginIDFromAttributes(attrs pcommon.Map) (originID string) { return } -// RunningTagsFromAttributes gets tags used for running metrics from attributes. -func RunningTagsFromAttributes(attrs pcommon.Map) []string { - tags := make([]string, 0, 1) - for _, key := range runningTagsAttributes { - if val, ok := attrs.Get(key); ok { - if ddKey, found := conventionsMapping[key]; found && val.StringVal() != "" { - tags = append(tags, fmt.Sprintf("%s:%s", ddKey, val.StringVal())) - } - } - } - return tags -} - // ContainerTagFromAttributes extracts the value of _dd.tags.container from the given // set of attributes. func ContainerTagFromAttributes(attr map[string]string) string { diff --git a/pkg/otlp/model/attributes/hostname.go b/pkg/otlp/model/attributes/source.go similarity index 80% rename from pkg/otlp/model/attributes/hostname.go rename to pkg/otlp/model/attributes/source.go index ade4fe237c5ba3..779fff205e99c2 100644 --- a/pkg/otlp/model/attributes/hostname.go +++ b/pkg/otlp/model/attributes/source.go @@ -21,6 +21,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/otlp/model/attributes/azure" "github.com/DataDog/datadog-agent/pkg/otlp/model/attributes/ec2" "github.com/DataDog/datadog-agent/pkg/otlp/model/attributes/gcp" + "github.com/DataDog/datadog-agent/pkg/otlp/model/source" ) const ( @@ -55,7 +56,7 @@ func getClusterName(attrs pcommon.Map) (string, bool) { // 6. the host.name attribute. // // It returns a boolean value indicated if any name was found -func HostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) (string, bool) { +func hostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) (string, bool) { // Check if the host is localhost or 0.0.0.0, if so discard it. // We don't do the more strict validation done for metadata, // to avoid breaking users existing invalid-but-accepted hostnames. @@ -75,6 +76,17 @@ func HostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) (string, bo return candidateHost, ok } +func k8sHostnameFromAttributes(attrs pcommon.Map) (string, bool) { + if k8sNodeName, ok := attrs.Get(AttributeK8sNodeName); ok { + if k8sClusterName, ok := getClusterName(attrs); ok { + return k8sNodeName.StringVal() + "-" + k8sClusterName, true + } + return k8sNodeName.StringVal(), true + } + + return "", false +} + func unsanitizedHostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) (string, bool) { // Custom hostname: useful for overriding in k8s/cloud envs if customHostname, ok := attrs.Get(AttributeDatadogHostname); ok { @@ -82,16 +94,15 @@ func unsanitizedHostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) } if launchType, ok := attrs.Get(conventions.AttributeAWSECSLaunchtype); ok && launchType.StringVal() == conventions.AttributeAWSECSLaunchtypeFargate { - // If on AWS ECS Fargate, return a valid but empty hostname - return "", true + // If on AWS ECS Fargate, we don't have a hostname + return "", false } // Kubernetes: node-cluster if cluster name is available, else node - if k8sNodeName, ok := attrs.Get(AttributeK8sNodeName); ok { - if k8sClusterName, ok := getClusterName(attrs); ok { - return k8sNodeName.StringVal() + "-" + k8sClusterName, true - } - return k8sNodeName.StringVal(), true + k8sName, k8sOk := k8sHostnameFromAttributes(attrs) + + if !usePreviewRules && k8sOk { + return k8sName, true } cloudProvider, ok := attrs.Get(conventions.AttributeCloudProvider) @@ -103,6 +114,10 @@ func unsanitizedHostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) return azure.HostnameFromAttributes(attrs, usePreviewRules) } + if usePreviewRules && k8sOk { + return k8sName, true + } + // host id from cloud provider if hostID, ok := attrs.Get(conventions.AttributeHostID); ok { return hostID.StringVal(), true @@ -122,3 +137,18 @@ func unsanitizedHostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) return "", false } + +// SourceFromAttributes gets a telemetry signal source from its attributes. +func SourceFromAttributes(attrs pcommon.Map, usePreviewRules bool) (source.Source, bool) { + if launchType, ok := attrs.Get(conventions.AttributeAWSECSLaunchtype); ok && launchType.StringVal() == conventions.AttributeAWSECSLaunchtypeFargate { + if taskARN, ok := attrs.Get(conventions.AttributeAWSECSTaskARN); ok { + return source.Source{Kind: source.AWSECSFargateKind, Identifier: taskARN.StringVal()}, true + } + } + + if host, ok := hostnameFromAttributes(attrs, usePreviewRules); ok { + return source.Source{Kind: source.HostnameKind, Identifier: host}, true + } + + return source.Source{}, false +} diff --git a/pkg/otlp/model/attributes/hostname_test.go b/pkg/otlp/model/attributes/source_test.go similarity index 76% rename from pkg/otlp/model/attributes/hostname_test.go rename to pkg/otlp/model/attributes/source_test.go index 60612eeffa0351..e9d80506c0eb72 100644 --- a/pkg/otlp/model/attributes/hostname_test.go +++ b/pkg/otlp/model/attributes/source_test.go @@ -23,6 +23,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/otlp/model/attributes/azure" "github.com/DataDog/datadog-agent/pkg/otlp/model/internal/testutils" + "github.com/DataDog/datadog-agent/pkg/otlp/model/source" ) const ( @@ -37,14 +38,14 @@ const ( testGCPIntegrationHostname = testHostName + "." + testCloudAccount ) -func TestHostnameFromAttributes(t *testing.T) { +func TestSourceFromAttributes(t *testing.T) { tests := []struct { name string attrs pcommon.Map usePreview bool - ok bool - hostname string + ok bool + src source.Source }{ { name: "custom hostname", @@ -56,16 +57,16 @@ func TestHostnameFromAttributes(t *testing.T) { conventions.AttributeHostID: testHostID, conventions.AttributeHostName: testHostName, }), - ok: true, - hostname: testCustomName, + ok: true, + src: source.Source{Kind: source.HostnameKind, Identifier: testCustomName}, }, { name: "container ID", attrs: testutils.NewAttributeMap(map[string]string{ conventions.AttributeContainerID: testContainerID, }), - ok: true, - hostname: testContainerID, + ok: true, + src: source.Source{Kind: source.HostnameKind, Identifier: testContainerID}, }, { name: "container ID, preview", @@ -81,8 +82,8 @@ func TestHostnameFromAttributes(t *testing.T) { conventions.AttributeHostID: testHostID, conventions.AttributeHostName: testHostName, }), - ok: true, - hostname: testHostName, + ok: true, + src: source.Source{Kind: source.HostnameKind, Identifier: testHostName}, }, { name: "AWS EC2, preview", @@ -92,7 +93,7 @@ func TestHostnameFromAttributes(t *testing.T) { conventions.AttributeHostName: testHostName, }), ok: true, - hostname: testHostID, + src: source.Source{Kind: source.HostnameKind, Identifier: testHostID}, usePreview: true, }, { @@ -105,8 +106,8 @@ func TestHostnameFromAttributes(t *testing.T) { conventions.AttributeAWSECSTaskRevision: "example-task-revision", conventions.AttributeAWSECSLaunchtype: conventions.AttributeAWSECSLaunchtypeFargate, }), - ok: true, - hostname: "", + ok: true, + src: source.Source{Kind: source.AWSECSFargateKind, Identifier: "example-task-ARN"}, }, { name: "GCP", @@ -115,8 +116,8 @@ func TestHostnameFromAttributes(t *testing.T) { conventions.AttributeHostID: testHostID, conventions.AttributeHostName: testGCPHostname, }), - ok: true, - hostname: testGCPHostname, + ok: true, + src: source.Source{Kind: source.HostnameKind, Identifier: testGCPHostname}, }, { name: "GCP, preview", @@ -128,7 +129,7 @@ func TestHostnameFromAttributes(t *testing.T) { }), usePreview: true, ok: true, - hostname: testGCPIntegrationHostname, + src: source.Source{Kind: source.HostnameKind, Identifier: testGCPIntegrationHostname}, }, { name: "azure", @@ -137,8 +138,8 @@ func TestHostnameFromAttributes(t *testing.T) { conventions.AttributeHostID: testHostID, conventions.AttributeHostName: testHostName, }), - ok: true, - hostname: testHostName, + ok: true, + src: source.Source{Kind: source.HostnameKind, Identifier: testHostName}, }, { name: "azure, preview", @@ -149,7 +150,7 @@ func TestHostnameFromAttributes(t *testing.T) { }), usePreview: true, ok: true, - hostname: testHostID, + src: source.Source{Kind: source.HostnameKind, Identifier: testHostID}, }, { name: "host id v. hostname", @@ -157,8 +158,8 @@ func TestHostnameFromAttributes(t *testing.T) { conventions.AttributeHostID: testHostID, conventions.AttributeHostName: testHostName, }), - ok: true, - hostname: testHostID, + ok: true, + src: source.Source{Kind: source.HostnameKind, Identifier: testHostID}, }, { name: "no hostname", @@ -174,9 +175,9 @@ func TestHostnameFromAttributes(t *testing.T) { for _, testInstance := range tests { t.Run(testInstance.name, func(t *testing.T) { - hostname, ok := HostnameFromAttributes(testInstance.attrs, testInstance.usePreview) + source, ok := SourceFromAttributes(testInstance.attrs, testInstance.usePreview) assert.Equal(t, testInstance.ok, ok) - assert.Equal(t, testInstance.hostname, hostname) + assert.Equal(t, testInstance.src, source) }) } @@ -224,7 +225,7 @@ func TestHostnameKubernetes(t *testing.T) { conventions.AttributeHostID: testHostID, conventions.AttributeHostName: testHostName, }) - hostname, ok := HostnameFromAttributes(attrs, false) + hostname, ok := hostnameFromAttributes(attrs, false) assert.True(t, ok) assert.Equal(t, hostname, "nodeName-clusterName") @@ -235,10 +236,34 @@ func TestHostnameKubernetes(t *testing.T) { conventions.AttributeHostID: testHostID, conventions.AttributeHostName: testHostName, }) - hostname, ok = HostnameFromAttributes(attrs, false) + hostname, ok = hostnameFromAttributes(attrs, false) assert.True(t, ok) assert.Equal(t, hostname, "nodeName") + // Node name, no cluster name, AWS EC2, no preview + attrs = testutils.NewAttributeMap(map[string]string{ + AttributeK8sNodeName: testNodeName, + conventions.AttributeContainerID: testContainerID, + conventions.AttributeHostID: testHostID, + conventions.AttributeHostName: testHostName, + conventions.AttributeCloudProvider: conventions.AttributeCloudProviderAWS, + }) + hostname, ok = hostnameFromAttributes(attrs, false) + assert.True(t, ok) + assert.Equal(t, hostname, "nodeName") + + // Node name, no cluster name, AWS EC2, preview + attrs = testutils.NewAttributeMap(map[string]string{ + AttributeK8sNodeName: testNodeName, + conventions.AttributeContainerID: testContainerID, + conventions.AttributeHostID: testHostID, + conventions.AttributeHostName: testHostName, + conventions.AttributeCloudProvider: conventions.AttributeCloudProviderAWS, + }) + hostname, ok = hostnameFromAttributes(attrs, true) + assert.True(t, ok) + assert.Equal(t, hostname, testHostID) + // no node name, cluster name attrs = testutils.NewAttributeMap(map[string]string{ conventions.AttributeK8SClusterName: testClusterName, @@ -246,7 +271,7 @@ func TestHostnameKubernetes(t *testing.T) { conventions.AttributeHostID: testHostID, conventions.AttributeHostName: testHostName, }) - hostname, ok = HostnameFromAttributes(attrs, false) + hostname, ok = hostnameFromAttributes(attrs, false) assert.True(t, ok) // cluster name gets ignored, fallback to next option assert.Equal(t, hostname, testHostID) diff --git a/pkg/otlp/model/source/source_provider.go b/pkg/otlp/model/source/source_provider.go new file mode 100644 index 00000000000000..4f686f9bb884a7 --- /dev/null +++ b/pkg/otlp/model/source/source_provider.go @@ -0,0 +1,51 @@ +// 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 source + +import ( + "context" + "fmt" +) + +// Kind of source +type Kind string + +const ( + // InvalidKind is an invalid kind. It is the zero value of Kind. + InvalidKind Kind = "" + // HostnameKind is a host source. + HostnameKind Kind = "host" + // AWSECSFargateKind is a serverless source on AWS ECS Fargate. + AWSECSFargateKind Kind = "task_arn" +) + +// Source represents a telemetry source. +type Source struct { + // Kind of source (serverless v. host). + Kind Kind + // Identifier that uniquely determines the source. + Identifier string +} + +// Tag associated to a source. +func (s *Source) Tag() string { + return fmt.Sprintf("%s:%s", s.Kind, s.Identifier) +} + +// Provider identifies a source. +type Provider interface { + // Source gets the source from the current context. + Source(ctx context.Context) (Source, error) +} diff --git a/pkg/otlp/model/translator/config.go b/pkg/otlp/model/translator/config.go index 699e96c180801d..2734430b233901 100644 --- a/pkg/otlp/model/translator/config.go +++ b/pkg/otlp/model/translator/config.go @@ -14,7 +14,11 @@ package translator -import "fmt" +import ( + "fmt" + + "github.com/DataDog/datadog-agent/pkg/otlp/model/source" +) type translatorConfig struct { // metrics export behavior @@ -35,7 +39,7 @@ type translatorConfig struct { // hostname provider configuration previewHostnameFromAttributes bool - fallbackHostnameProvider HostnameProvider + fallbackSourceProvider source.Provider } // Option is a translator creation option. @@ -57,11 +61,11 @@ func WithDeltaTTL(deltaTTL int64) Option { } } -// WithFallbackHostnameProvider sets the fallback hostname provider. +// WithFallbackSourceProvider sets the fallback hostname provider. // By default, an empty hostname is used as a fallback. -func WithFallbackHostnameProvider(provider HostnameProvider) Option { +func WithFallbackSourceProvider(provider source.Provider) Option { return func(t *translatorConfig) error { - t.fallbackHostnameProvider = provider + t.fallbackSourceProvider = provider return nil } } diff --git a/pkg/otlp/model/translator/hostname_provider.go b/pkg/otlp/model/translator/hostname_provider.go deleted file mode 100644 index 829d0654add262..00000000000000 --- a/pkg/otlp/model/translator/hostname_provider.go +++ /dev/null @@ -1,31 +0,0 @@ -// 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 translator - -import "context" - -// HostnameProvider gets a hostname -type HostnameProvider interface { - // Hostname gets the hostname from the machine. - Hostname(ctx context.Context) (string, error) -} - -var _ HostnameProvider = (*noHostProvider)(nil) - -type noHostProvider struct{} - -func (*noHostProvider) Hostname(context.Context) (string, error) { - return "", nil -} diff --git a/pkg/otlp/model/translator/metrics_translator.go b/pkg/otlp/model/translator/metrics_translator.go index 4853ea0ea8b82f..cd06b655e9d2cb 100644 --- a/pkg/otlp/model/translator/metrics_translator.go +++ b/pkg/otlp/model/translator/metrics_translator.go @@ -23,6 +23,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/otlp/model/attributes" "github.com/DataDog/datadog-agent/pkg/otlp/model/internal/instrumentationlibrary" "github.com/DataDog/datadog-agent/pkg/otlp/model/internal/instrumentationscope" + "github.com/DataDog/datadog-agent/pkg/otlp/model/source" "github.com/DataDog/datadog-agent/pkg/quantile" "github.com/DataDog/sketches-go/ddsketch" "github.com/DataDog/sketches-go/ddsketch/mapping" @@ -33,6 +34,14 @@ import ( const metricName string = "metric name" +var _ source.Provider = (*noSourceProvider)(nil) + +type noSourceProvider struct{} + +func (*noSourceProvider) Source(context.Context) (source.Source, error) { + return source.Source{Kind: source.HostnameKind, Identifier: ""}, nil +} + // Translator is a metrics translator. type Translator struct { prevPts *ttlCache @@ -51,7 +60,7 @@ func New(logger *zap.Logger, options ...Option) (*Translator, error) { InstrumentationLibraryMetadataAsTags: false, sweepInterval: 1800, deltaTTL: 3600, - fallbackHostnameProvider: &noHostProvider{}, + fallbackSourceProvider: &noSourceProvider{}, } for _, opt := range options { @@ -523,27 +532,25 @@ func (t *Translator) MapMetrics(ctx context.Context, md pmetric.Metrics, consume // Fetch tags from attributes. attributeTags := attributes.TagsFromAttributes(rm.Resource().Attributes()) - host, ok := attributes.HostnameFromAttributes(rm.Resource().Attributes(), t.cfg.previewHostnameFromAttributes) + src, ok := attributes.SourceFromAttributes(rm.Resource().Attributes(), t.cfg.previewHostnameFromAttributes) if !ok { var err error - host, err = t.cfg.fallbackHostnameProvider.Hostname(context.Background()) + src, err = t.cfg.fallbackSourceProvider.Source(context.Background()) if err != nil { return fmt.Errorf("failed to get fallback host: %w", err) } } - if host != "" { - // Track hosts if the consumer is a HostConsumer. + var host string + switch src.Kind { + case source.HostnameKind: + host = src.Identifier if c, ok := consumer.(HostConsumer); ok { c.ConsumeHost(host) } - } else { - // Track task ARN if the consumer is a TagsConsumer. + case source.AWSECSFargateKind: if c, ok := consumer.(TagsConsumer); ok { - tags := attributes.RunningTagsFromAttributes(rm.Resource().Attributes()) - for _, tag := range tags { - c.ConsumeTag(tag) - } + c.ConsumeTag(src.Tag()) } } diff --git a/pkg/otlp/model/translator/metrics_translator_test.go b/pkg/otlp/model/translator/metrics_translator_test.go index 12c745df9ff0ce..898b3423165d0a 100644 --- a/pkg/otlp/model/translator/metrics_translator_test.go +++ b/pkg/otlp/model/translator/metrics_translator_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/DataDog/datadog-agent/pkg/otlp/model/attributes" + "github.com/DataDog/datadog-agent/pkg/otlp/model/source" "github.com/DataDog/datadog-agent/pkg/quantile" "github.com/DataDog/datadog-agent/pkg/quantile/summary" gocache "github.com/patrickmn/go-cache" @@ -89,15 +90,20 @@ func TestIsCumulativeMonotonic(t *testing.T) { } } +var _ source.Provider = (*testProvider)(nil) + type testProvider string -func (t testProvider) Hostname(context.Context) (string, error) { - return string(t), nil +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, opts ...Option) *Translator { options := append([]Option{ - WithFallbackHostnameProvider(testProvider("fallbackHostname")), + WithFallbackSourceProvider(testProvider("fallbackHostname")), WithHistogramMode(HistogramModeDistributions), WithNumberMode(NumberModeCumulativeToDelta), }, opts...) @@ -870,7 +876,7 @@ func TestMapSummaryMetrics(t *testing.T) { c := newTestCache() c.cache.Set((&Dimensions{name: "summary.example.count", tags: tags}).String(), numberCounter{0, 0, 1}, gocache.NoExpiration) c.cache.Set((&Dimensions{name: "summary.example.sum", tags: tags}).String(), numberCounter{0, 0, 1}, gocache.NoExpiration) - options := []Option{WithFallbackHostnameProvider(testProvider("fallbackHostname"))} + options := []Option{WithFallbackSourceProvider(testProvider("fallbackHostname"))} if quantiles { options = append(options, WithQuantiles()) } From 99f017035e91e5fec1ffcadd73d34de63ef738ed Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Mon, 20 Jun 2022 12:03:03 +0200 Subject: [PATCH 2/6] Adapt serializer and trace OTLP code to new changes --- .../internal/serializerexporter/exporter.go | 20 +++++---- pkg/trace/api/otlp.go | 41 ++++++++++--------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/pkg/otlp/internal/serializerexporter/exporter.go b/pkg/otlp/internal/serializerexporter/exporter.go index fd06b86c33f2f4..e973095976e567 100644 --- a/pkg/otlp/internal/serializerexporter/exporter.go +++ b/pkg/otlp/internal/serializerexporter/exporter.go @@ -9,6 +9,7 @@ import ( "context" "fmt" + "github.com/DataDog/datadog-agent/pkg/otlp/model/source" "github.com/DataDog/datadog-agent/pkg/otlp/model/translator" "github.com/DataDog/datadog-agent/pkg/serializer" "github.com/DataDog/datadog-agent/pkg/tagger/collectors" @@ -51,14 +52,19 @@ func newDefaultConfig() config.Exporter { } } -var _ translator.HostnameProvider = (*hostnameProviderFunc)(nil) +var _ source.Provider = (*sourceProviderFunc)(nil) -// hostnameProviderFunc is an adapter to allow the use of a function as a translator.HostnameProvider. -type hostnameProviderFunc func(context.Context) (string, error) +// sourceProviderFunc is an adapter to allow the use of a function as a translator.HostnameProvider. +type sourceProviderFunc func(context.Context) (string, error) -// Hostname calls f. -func (f hostnameProviderFunc) Hostname(ctx context.Context) (string, error) { - return f(ctx) +// Source calls f and wraps in a source struct. +func (f sourceProviderFunc) Source(ctx context.Context) (source.Source, error) { + hostname, err := f(ctx) + if err != nil { + return source.Source{}, err + } + + return source.Source{Kind: source.HostnameKind, Identifier: hostname}, nil } // exporter translate OTLP metrics into the Datadog format and sends @@ -81,7 +87,7 @@ func translatorFromConfig(logger *zap.Logger, cfg *exporterConfig) (*translator. } options := []translator.Option{ - translator.WithFallbackHostnameProvider(hostnameProviderFunc(util.GetHostname)), + translator.WithFallbackSourceProvider(sourceProviderFunc(util.GetHostname)), translator.WithHistogramMode(histogramMode), translator.WithDeltaTTL(cfg.Metrics.DeltaTTL), } diff --git a/pkg/trace/api/otlp.go b/pkg/trace/api/otlp.go index c93242b128ebde..d89f06147346f8 100644 --- a/pkg/trace/api/otlp.go +++ b/pkg/trace/api/otlp.go @@ -20,6 +20,7 @@ import ( "time" "github.com/DataDog/datadog-agent/pkg/otlp/model/attributes" + "github.com/DataDog/datadog-agent/pkg/otlp/model/source" "github.com/DataDog/datadog-agent/pkg/trace/api/apiutil" "github.com/DataDog/datadog-agent/pkg/trace/config" "github.com/DataDog/datadog-agent/pkg/trace/info" @@ -207,17 +208,8 @@ func (o *OTLPReceiver) processRequest(protocol string, header http.Header, in pt } } -// OTLPIngestSummary returns a summary of the received resource spans. -type OTLPIngestSummary struct { - // Hostname indicates the hostname of the passed resource spans. - Hostname string - // Tags returns a set of Datadog-specific tags which are relevant for identifying - // the source of the passed resource spans. - Tags []string -} - // ReceiveResourceSpans processes the given rspans and sends them to writer. -func (o *OTLPReceiver) ReceiveResourceSpans(rspans ptrace.ResourceSpans, header http.Header, protocol string) OTLPIngestSummary { +func (o *OTLPReceiver) ReceiveResourceSpans(rspans ptrace.ResourceSpans, header http.Header, protocol string) source.Source { // each rspans is coming from a different resource and should be considered // a separate payload; typically there is only one item in this slice attr := rspans.Resource().Attributes() @@ -226,15 +218,15 @@ func (o *OTLPReceiver) ReceiveResourceSpans(rspans ptrace.ResourceSpans, header rattr[k] = v.AsString() return true }) - hostname, hostok := attributes.HostnameFromAttributes(attr, o.conf.OTLPReceiver.UsePreviewHostnameLogic) + src, srcok := attributes.SourceFromAttributes(attr, o.conf.OTLPReceiver.UsePreviewHostnameLogic) hostFromMap := func(m map[string]string, key string) { // hostFromMap sets the hostname to m[key] if it is set. if v, ok := m[key]; ok { - hostname = v - hostok = true + src = source.Source{Kind: source.HostnameKind, Identifier: v} + srcok = true } } - if !hostok { + if !srcok { hostFromMap(rattr, "_dd.hostname") } env := rattr[string(semconv.AttributeDeploymentEnvironment)] @@ -272,7 +264,7 @@ func (o *OTLPReceiver) ReceiveResourceSpans(rspans ptrace.ResourceSpans, header tracesByID[traceID] = pb.Trace{} } ddspan := o.convertSpan(rattr, lib, span) - if !hostok { + if !srcok { // if we didn't find a hostname at the resource level // try and see if the span has a hostname set hostFromMap(ddspan.Meta, "_dd.hostname") @@ -318,9 +310,21 @@ func (o *OTLPReceiver) ReceiveResourceSpans(rspans ptrace.ResourceSpans, header if env == "" { env = o.conf.DefaultEnv } - if !hostok { + + // Get the hostname or set to empty + // if source is empty + var hostname string + if srcok { + switch src.Kind { + case source.HostnameKind: + hostname = src.Identifier + default: + hostname = "" + } + } else { hostname = o.conf.Hostname } + p.TracerPayload = &pb.TracerPayload{ Hostname: hostname, Chunks: traceChunks, @@ -341,10 +345,7 @@ func (o *OTLPReceiver) ReceiveResourceSpans(rspans ptrace.ResourceSpans, header default: log.Warn("Payload in channel full. Dropped 1 payload.") } - return OTLPIngestSummary{ - Hostname: hostname, - Tags: attributes.RunningTagsFromAttributes(attr), - } + return src } // marshalEvents marshals events into JSON. From cf524f0b66f44b52264e9aad49582b4245763ad2 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Tue, 21 Jun 2022 10:57:34 +0200 Subject: [PATCH 3/6] Address comments on Kubernetes code --- pkg/otlp/model/attributes/source.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/otlp/model/attributes/source.go b/pkg/otlp/model/attributes/source.go index 779fff205e99c2..004d917c5fd493 100644 --- a/pkg/otlp/model/attributes/source.go +++ b/pkg/otlp/model/attributes/source.go @@ -77,14 +77,15 @@ func hostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) (string, bo } func k8sHostnameFromAttributes(attrs pcommon.Map) (string, bool) { - if k8sNodeName, ok := attrs.Get(AttributeK8sNodeName); ok { - if k8sClusterName, ok := getClusterName(attrs); ok { - return k8sNodeName.StringVal() + "-" + k8sClusterName, true - } - return k8sNodeName.StringVal(), true + node, ok := attrs.Get(AttributeK8sNodeName) + if !ok { + return "", false } - return "", false + if cluster, ok := getClusterName(attrs); ok { + return node.StringVal() + "-" + cluster, true + } + return node.StringVal(), true } func unsanitizedHostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) (string, bool) { @@ -101,6 +102,8 @@ func unsanitizedHostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) // Kubernetes: node-cluster if cluster name is available, else node k8sName, k8sOk := k8sHostnameFromAttributes(attrs) + // If not using the preview rules, return the Kubernetes node name + // before cloud provider names to preserve the current behavior. if !usePreviewRules && k8sOk { return k8sName, true } @@ -114,6 +117,8 @@ func unsanitizedHostnameFromAttributes(attrs pcommon.Map, usePreviewRules bool) return azure.HostnameFromAttributes(attrs, usePreviewRules) } + // If using the preview rules, the cloud provider names take precedence. + // This is to report the same hostname as Datadog cloud integrations. if usePreviewRules && k8sOk { return k8sName, true } From 93e8d8697d695ef75507b66b04ae077016a721e9 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Tue, 21 Jun 2022 11:40:15 +0200 Subject: [PATCH 4/6] [pkg/otlp/model] Rename to unknown source --- pkg/otlp/model/translator/metrics_translator.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/otlp/model/translator/metrics_translator.go b/pkg/otlp/model/translator/metrics_translator.go index cd06b655e9d2cb..194806990a138c 100644 --- a/pkg/otlp/model/translator/metrics_translator.go +++ b/pkg/otlp/model/translator/metrics_translator.go @@ -34,11 +34,12 @@ import ( const metricName string = "metric name" -var _ source.Provider = (*noSourceProvider)(nil) +var _ source.Provider = (*unknownSourceProvider)(nil) -type noSourceProvider struct{} +// unknownSourceProvider sets an empty hostname as a source. +type unknownSourceProvider struct{} -func (*noSourceProvider) Source(context.Context) (source.Source, error) { +func (*unknownSourceProvider) Source(context.Context) (source.Source, error) { return source.Source{Kind: source.HostnameKind, Identifier: ""}, nil } @@ -60,7 +61,7 @@ func New(logger *zap.Logger, options ...Option) (*Translator, error) { InstrumentationLibraryMetadataAsTags: false, sweepInterval: 1800, deltaTTL: 3600, - fallbackSourceProvider: &noSourceProvider{}, + fallbackSourceProvider: &unknownSourceProvider{}, } for _, opt := range options { From d91ba3e44dcc0a6a2d7f9bfdc54130c0669bd59a Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Tue, 21 Jun 2022 11:44:52 +0200 Subject: [PATCH 5/6] [pkg/trace] Address comments --- pkg/trace/api/otlp.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/trace/api/otlp.go b/pkg/trace/api/otlp.go index d89f06147346f8..f64254a63dd1f2 100644 --- a/pkg/trace/api/otlp.go +++ b/pkg/trace/api/otlp.go @@ -311,20 +311,19 @@ func (o *OTLPReceiver) ReceiveResourceSpans(rspans ptrace.ResourceSpans, header env = o.conf.DefaultEnv } - // Get the hostname or set to empty - // if source is empty + // Get the hostname or set to empty if source is empty var hostname string if srcok { switch src.Kind { case source.HostnameKind: hostname = src.Identifier default: + // We are not on a hostname (serverless), hence the hostname is empty hostname = "" } } else { hostname = o.conf.Hostname } - p.TracerPayload = &pb.TracerPayload{ Hostname: hostname, Chunks: traceChunks, From eb4ea06de1355723d42a13c6bbbbece65c986143 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Wed, 22 Jun 2022 10:52:20 +0200 Subject: [PATCH 6/6] update ReceiveResourceSpans comment --- pkg/trace/api/otlp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/trace/api/otlp.go b/pkg/trace/api/otlp.go index f64254a63dd1f2..51e02381c44a91 100644 --- a/pkg/trace/api/otlp.go +++ b/pkg/trace/api/otlp.go @@ -208,7 +208,7 @@ func (o *OTLPReceiver) processRequest(protocol string, header http.Header, in pt } } -// ReceiveResourceSpans processes the given rspans and sends them to writer. +// ReceiveResourceSpans processes the given rspans and returns the source that it identified from processing them. func (o *OTLPReceiver) ReceiveResourceSpans(rspans ptrace.ResourceSpans, header http.Header, protocol string) source.Source { // each rspans is coming from a different resource and should be considered // a separate payload; typically there is only one item in this slice