diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml index 514959d1..cff0639b 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -47,7 +47,7 @@ jobs: publish: env: - RELEASE_VERSION: ${{ github.event_name == 'pull_request' && 'dev-test' || 'dev' }} + RELEASE_VERSION: ${{ github.event_name == 'pull_request' && 'dev-test-upstream2' || 'dev' }} if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'publish-dev-test') )}} name: Publish pre-release needs: [build] diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 4deb696d..d1df51bb 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -40,12 +40,16 @@ import,github.com/google/pprof/profile,Apache-2.0,unknown import,github.com/google/uuid,BSD-3-Clause,"Copyright (c) 2009,2014 Google Inc. All rights reserved." import,github.com/jonboulle/clockwork,Apache-2.0,unknown import,github.com/josharian/intern,MIT,Copyright (c) 2019 Josh Bleecher Snyder +import,github.com/josharian/native,MIT,Copyright 2020 Josh Bleecher Snyder import,github.com/json-iterator/go,MIT,Copyright (c) 2016 json-iterator import,github.com/klauspost/compress,MIT,Copyright (c) 2012 The Go Authors. All rights reserved. | Copyright (c) 2019 Klaus Post. All rights reserved. | Copyright 2016-2017 The New York Times Company | Copyright (c) 2015 Klaus Post | Copyright (c) 2011 The Snappy-Go Authors. All rights reserved. | Copyright 2016 The filepathx Authors import,github.com/klauspost/compress/internal/snapref,BSD-3-Clause,Copyright (c) 2011 The Snappy-Go Authors. All rights reserved. import,github.com/klauspost/compress/zstd/internal/xxhash,MIT,Copyright (c) 2016 Caleb Spare import,github.com/klauspost/cpuid/v2,MIT,Copyright (c) 2015 Klaus Post import,github.com/mailru/easyjson,MIT,Copyright (c) 2016 Mail.Ru Group +import,github.com/mdlayher/kobject,MIT,Copyright (C) 2017 Matt Layher +import,github.com/mdlayher/netlink,MIT,Copyright (C) 2016-2022 Matt Layher +import,github.com/mdlayher/socket,MIT,Copyright (C) 2021 Matt Layher import,github.com/minio/sha256-simd,Apache-2.0,unknown import,github.com/moby/docker-image-spec/specs-go/v1,Apache-2.0,unknown import,github.com/moby/locker,Apache-2.0,"Copyright 2013-2018 Docker, Inc." @@ -80,7 +84,7 @@ import,go.opentelemetry.io/otel,Apache-2.0,unknown import,go.opentelemetry.io/otel/metric,Apache-2.0,unknown import,go.opentelemetry.io/otel/trace,Apache-2.0,unknown import,go.uber.org/multierr,MIT,"Copyright (c) 2017-2021 Uber Technologies, Inc." -import,golang.org/x/arch/arm64/arm64asm,BSD-3-Clause,Copyright 2015 The Go Authors. +import,golang.org/x/arch,BSD-3-Clause,Copyright 2015 The Go Authors. import,golang.org/x/exp/constraints,BSD-3-Clause,Copyright 2009 The Go Authors. import,golang.org/x/net,BSD-3-Clause,Copyright 2009 The Go Authors. import,golang.org/x/oauth2,BSD-3-Clause,Copyright 2009 The Go Authors. diff --git a/cli_flags.go b/cli_flags.go index 1b41a28b..b602766e 100644 --- a/cli_flags.go +++ b/cli_flags.go @@ -117,6 +117,7 @@ type arguments struct { agentless bool enableGoRuntimeProfiler bool enableSplitByService bool + collectContext bool splitServiceSuffix string cmd *cli.Command } @@ -393,6 +394,14 @@ func parseArgs() (*arguments, error) { Destination: &args.splitServiceSuffix, Sources: cli.EnvVars("DD_HOST_PROFILING_SPLIT_SERVICE_SUFFIX"), }, + &cli.BoolFlag{ + Name: "collect-context", + Value: false, + Hidden: true, + Usage: "Enable context collection.", + Destination: &args.collectContext, + Sources: cli.EnvVars("DD_HOST_PROFILING_COLLECT_CONTEXT"), + }, }, Action: func(_ context.Context, cmd *cli.Command) error { args.cmd = cmd diff --git a/containermetadata/container.go b/containermetadata/container.go index 63b47ed9..0614ea67 100644 --- a/containermetadata/container.go +++ b/containermetadata/container.go @@ -72,7 +72,8 @@ func (p *containerIDProvider) GetContainerMetadata(pid libpf.PID) (ContainerMeta isHostCgroupNamespace(cgroupNsFilePath)) } - return ContainerMetadata{ContainerID: containerID, EntityID: entityID}, nil + // Only fill EntityID, but not ContainerID, ContainerID is only used by containerMetadataProvider. + return ContainerMetadata{EntityID: entityID}, nil } // parseContainerID finds the first container ID reading from r and returns it. diff --git a/go.mod b/go.mod index 8cfee0b2..0257e23e 100644 --- a/go.mod +++ b/go.mod @@ -16,8 +16,8 @@ require ( github.com/urfave/cli/v3 v3.1.1 github.com/zeebo/xxh3 v1.0.2 go.opentelemetry.io/ebpf-profiler v0.0.0-20241114112653-6d846a2023a0 - golang.org/x/sync v0.12.0 - golang.org/x/sys v0.31.0 + golang.org/x/sync v0.16.0 + golang.org/x/sys v0.34.0 gopkg.in/DataDog/dd-trace-go.v1 v1.72.2 k8s.io/api v0.32.3 k8s.io/apimachinery v0.32.3 @@ -32,7 +32,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.11.7 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cilium/ebpf v0.16.0 // indirect + github.com/cilium/ebpf v0.19.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/containerd/api v1.8.0 // indirect github.com/containerd/continuity v0.4.4 // indirect @@ -47,11 +47,11 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/elastic/go-perf v0.0.0-20241016160959-1342461adb4a // indirect + github.com/elastic/go-perf v0.0.0-20241029065020-30bec95324b8 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -64,10 +64,14 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/josharian/native v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mdlayher/kobject v0.0.0-20200520190114-19ca17470d7d // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.4.1 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect @@ -78,7 +82,7 @@ require ( github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -89,30 +93,29 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/tklauser/numcpus v0.10.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/collector/consumer v1.22.0 // indirect - go.opentelemetry.io/collector/consumer/xconsumer v0.116.0 // indirect - go.opentelemetry.io/collector/pdata v1.22.0 // indirect - go.opentelemetry.io/collector/pdata/pprofile v0.116.0 // indirect + go.opentelemetry.io/collector/consumer v1.37.0 // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.131.0 // indirect + go.opentelemetry.io/collector/pdata v1.37.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.131.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/arch v0.10.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/arch v0.19.0 // indirect + golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.7.0 // indirect google.golang.org/genproto v0.0.0-20240325203815-454cdb8f5daa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect - google.golang.org/grpc v1.69.2 // indirect - google.golang.org/protobuf v1.36.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/grpc v1.74.2 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -128,4 +131,4 @@ require ( // To update the Datadog/opentelemetry-ebpf-profiler dependency on latest commit on datadog branch, change the following line to: // replace go.opentelemetry.io/ebpf-profiler => github.com/DataDog/opentelemetry-ebpf-profiler datadog // and run `go mod tidy` -replace go.opentelemetry.io/ebpf-profiler => github.com/DataDog/opentelemetry-ebpf-profiler v0.0.0-20250728155009-e8783b5032af +replace go.opentelemetry.io/ebpf-profiler => github.com/DataDog/opentelemetry-ebpf-profiler v0.0.0-20250807091316-7241ff91f321 diff --git a/go.sum b/go.sum index 24f6f061..629969d7 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/ github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= github.com/DataDog/jsonapi v0.12.0 h1:N4e9RpmUflcV5hzceltSz8XUpM3PMtQr5C9Bhv0g87s= github.com/DataDog/jsonapi v0.12.0/go.mod h1:FUSGF3bwMARlVfXEoFo9R/CVlYYy9BGL4C/Prf6Ke3M= -github.com/DataDog/opentelemetry-ebpf-profiler v0.0.0-20250728155009-e8783b5032af h1:BqRbIR0zfwMovN6pFVsHlh9nHnbCkA8qJVWf5rdiVhE= -github.com/DataDog/opentelemetry-ebpf-profiler v0.0.0-20250728155009-e8783b5032af/go.mod h1:zYNmE98qCkBe1U3RT8RakZE9zODxCDj6fc/rCaaCqK4= +github.com/DataDog/opentelemetry-ebpf-profiler v0.0.0-20250807091316-7241ff91f321 h1:n1UVfgp/cfDerVjCXQzBJleTpsurIqtQaS6TcXlVFao= +github.com/DataDog/opentelemetry-ebpf-profiler v0.0.0-20250807091316-7241ff91f321/go.mod h1:G0s9A43HDgTO5XuELD0pbSzuX5jPUcJ6nH2UBwVTdQI= github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 h1:fKv05WFWHCXQmUTehW1eEZvXJP65Qv00W4V01B1EqSA= github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0/go.mod h1:dvIWN9pA2zWNTw5rhDWZgzZnhcfpH++d+8d1SWW6xkY= github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE= @@ -54,8 +54,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= -github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= -github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= +github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao= +github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= @@ -102,8 +102,8 @@ github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxq github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= github.com/elastic/go-freelru v0.16.0 h1:gG2HJ1WXN2tNl5/p40JS/l59HjvjRhjyAa+oFTRArYs= github.com/elastic/go-freelru v0.16.0/go.mod h1:bSdWT4M0lW79K8QbX6XY2heQYSCqD7THoYf82pT/H3I= -github.com/elastic/go-perf v0.0.0-20241016160959-1342461adb4a h1:ymmtaN4bVCmKKeu4XEf6JEWNZKRXPMng1zjpKd+8rCU= -github.com/elastic/go-perf v0.0.0-20241016160959-1342461adb4a/go.mod h1:Nt+pnRYvf0POC+7pXsrv8ubsEOSsaipJP0zlz1Ms1RM= +github.com/elastic/go-perf v0.0.0-20241029065020-30bec95324b8 h1:FD01NjsTes0RxZVQ22ebNYJA4KDdInVnR9cn1hmaMwA= +github.com/elastic/go-perf v0.0.0-20241029065020-30bec95324b8/go.mod h1:Nt+pnRYvf0POC+7pXsrv8ubsEOSsaipJP0zlz1Ms1RM= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -115,8 +115,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= @@ -129,8 +129,8 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= -github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -159,6 +159,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -182,6 +183,8 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9 github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= @@ -190,15 +193,17 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= -github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= -github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 h1:nwOc1YaOrYJ37sEBrtWZrdqzK22hiJs3GpDmP3sR2Yw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink/v2 v2.0.3 h1:Jcp7GTnTPepoUAJ9+LhTa7ZiebvNS56T1GtlEUaPNFE= +github.com/jsimonetti/rtnetlink/v2 v2.0.3/go.mod h1:atIkksp/9fqtf6rpAw45JnttnP2gtuH9X88WPfWfS9A= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -214,6 +219,11 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= +github.com/mdlayher/kobject v0.0.0-20200520190114-19ca17470d7d h1:JmrZTpS0GAyMV4ZQVVH/AS0Y6r2PbnYNSRUuRX+HOLA= +github.com/mdlayher/kobject v0.0.0-20200520190114-19ca17470d7d/go.mod h1:+SexPO1ZvdbbWUdUnyXEWv3+4NwHZjKhxOmQqHY4Pqc= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= @@ -241,8 +251,9 @@ github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFL github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -329,36 +340,42 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/collector/component v0.116.0 h1:SQE1YeVfYCN7bw1n4hknUwJE5U/1qJL552sDhAdSlaA= -go.opentelemetry.io/collector/component v0.116.0/go.mod h1:MYgXFZWDTq0uPgF1mkLSFibtpNqksRVAOrmihckOQEs= -go.opentelemetry.io/collector/config/configtelemetry v0.116.0 h1:Vl49VCHQwBOeMswDpFwcl2HD8e9y94xlrfII3SR2VeQ= -go.opentelemetry.io/collector/config/configtelemetry v0.116.0/go.mod h1:SlBEwQg0qly75rXZ6W1Ig8jN25KBVBkFIIAUI1GiAAE= -go.opentelemetry.io/collector/consumer v1.22.0 h1:QmfnNizyNZFt0uK3GG/EoT5h6PvZJ0dgVTc5hFEc1l0= -go.opentelemetry.io/collector/consumer v1.22.0/go.mod h1:tiz2khNceFAPokxxfzAuFfIpShBasMT2AL2Sbc7+m0I= -go.opentelemetry.io/collector/consumer/xconsumer v0.116.0 h1:ZrWvq7HumB0jRYmS2ztZ3hhXRNpUVBWPKMbPhsVGmZM= -go.opentelemetry.io/collector/consumer/xconsumer v0.116.0/go.mod h1:C+VFMk8vLzPun6XK8aMts6h4RaDjmzXHCPaiOxzRQzQ= -go.opentelemetry.io/collector/pdata v1.22.0 h1:3yhjL46NLdTMoP8rkkcE9B0pzjf2973crn0KKhX5UrI= -go.opentelemetry.io/collector/pdata v1.22.0/go.mod h1:nLLf6uDg8Kn5g3WNZwGyu8+kf77SwOqQvMTb5AXEbEY= -go.opentelemetry.io/collector/pdata/pprofile v0.116.0 h1:iE6lqkO7Hi6lTIIml1RI7yQ55CKqW12R2qHinwF5Zuk= -go.opentelemetry.io/collector/pdata/pprofile v0.116.0/go.mod h1:xQiPpjzIiXRFb+1fPxUy/3ygEZgo0Bu/xmLKOWu8vMQ= +go.opentelemetry.io/collector/component v1.37.0 h1:yc5X0WhZwlpJ+W8Sg1fpRRjiUu3nByLe1wVOKWWRWRQ= +go.opentelemetry.io/collector/component v1.37.0/go.mod h1:SYHTXOzZLFwX075LEU6FMVBT15reVrwKHNB2En2URro= +go.opentelemetry.io/collector/consumer v1.37.0 h1:RqTqEcc95Fg7T3MRPPjUX2nxzn1X88yfFUQV+AjdMK0= +go.opentelemetry.io/collector/consumer v1.37.0/go.mod h1:vDA1JDXeb7vnQ02PXIjjR6dI9LTaya+Qr89Nyt2Gl7Y= +go.opentelemetry.io/collector/consumer/xconsumer v0.131.0 h1:PgCoBVF5FN87Ef2wDqLpRU7QxxIDs8dNiy9jKNdpWzk= +go.opentelemetry.io/collector/consumer/xconsumer v0.131.0/go.mod h1:xh1XRXcwk4Hxm3KSUCw/IOA0dyEoZr7Q/h0gzLnYaQo= +go.opentelemetry.io/collector/featuregate v1.37.0 h1:CjsHzjktiqq/dxid4Xkhuf3yD6oB/c7yRBWhokBJqpE= +go.opentelemetry.io/collector/featuregate v1.37.0/go.mod h1:Y/KsHbvREENKvvN9RlpiWk/IGBK+CATBYzIIpU7nccc= +go.opentelemetry.io/collector/internal/telemetry v0.131.0 h1:dqKbiGpcO8V31aWq2GRQLO/eNCs2B1IGS+qbkPFkmyc= +go.opentelemetry.io/collector/internal/telemetry v0.131.0/go.mod h1:TzNVIkIolnk/Jq/3qc4uWhL0bOeaP56jpyrMlUOeA/Y= +go.opentelemetry.io/collector/pdata v1.37.0 h1:aEEpd03GgAS352xntcYMsaxYvRXvzqEWqdrSro+TSh4= +go.opentelemetry.io/collector/pdata v1.37.0/go.mod h1:aE9l1Lcdsg7nmSoiucnWHuPYIk6T0RKzOjPepNJC5AQ= +go.opentelemetry.io/collector/pdata/pprofile v0.131.0 h1:eQ2Yq1g6wOWHjRXum9Fm0dZax/klNmjtpL7UPsEXrPo= +go.opentelemetry.io/collector/pdata/pprofile v0.131.0/go.mod h1:g4IuRFVGC89n/2bTdw0CuMJkkCY4zDb0Hu37wCKlx0c= go.opentelemetry.io/collector/semconv v0.104.0 h1:dUvajnh+AYJLEW/XOPk0T0BlwltSdi3vrjO7nSOos3k= go.opentelemetry.io/collector/semconv v0.104.0/go.mod h1:yMVUCNoQPZVq/IPfrHrnntZTWsLf5YGZ7qwKulIl5hw= +go.opentelemetry.io/contrib/bridges/otelzap v0.12.0 h1:FGre0nZh5BSw7G73VpT3xs38HchsfPsa2aZtMp0NPOs= +go.opentelemetry.io/contrib/bridges/otelzap v0.12.0/go.mod h1:X2PYPViI2wTPIMIOBjG17KNybTzsrATnvPJ02kkz7LM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= -go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/log v0.13.0 h1:yoxRoIZcohB6Xf0lNv9QIyCzQvrtGZklVbdCoyb7dls= +go.opentelemetry.io/otel/log v0.13.0/go.mod h1:INKfG4k1O9CL25BaM1qLe0zIedOpvlS5Z7XgSbmN83E= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -369,49 +386,57 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= -golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU= +golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -421,15 +446,15 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -441,8 +466,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -456,17 +481,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240325203815-454cdb8f5daa h1:ePqxpG3LVx+feAUOx8YmR5T7rc0rdzK8DyxM8cQ9zq0= google.golang.org/genproto v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:CnZenrTdRJb7jc+jOm0Rkywq+9wh0QC4U8tyiRbEPPM= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= -google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -479,8 +504,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/DataDog/dd-trace-go.v1 v1.72.2 h1:SLcih9LB+I1l76Wd7aUSpzISemewzjq6djntMnBnzkA= gopkg.in/DataDog/dd-trace-go.v1 v1.72.2/go.mod h1:XqDhDqsLpThFnJc4z0FvAEItISIAUka+RHwmQ6EfN1U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index e25d96f7..784e1b04 100644 --- a/main.go +++ b/main.go @@ -66,7 +66,6 @@ const ( // Go 'flag' package calls os.Exit(2) on flag parse errors, if ExitOnError is set exitParseError exitCode = 2 - defaultFramesCacheSize = 65536 defaultExecutablesCacheSize = 65536 defaultProcessesCacheSize = 16384 ) @@ -175,6 +174,15 @@ func mainWithExitCode() exitCode { return failure("Failed to parse the included tracers: %v", err) } + // Disable Go interpreter because we are doing Go symbolization remotely. + includeTracers.Disable(tracertypes.GoTracer) + if args.collectContext { + includeTracers.Enable(tracertypes.Labels) + } else { + includeTracers.Disable(tracertypes.Labels) + } + log.Infof("Enabled tracers: %s", includeTracers.String()) + validatedTags := ValidateTags(args.tags) log.Debugf("Validated tags: %s", validatedTags) @@ -230,17 +238,15 @@ func mainWithExitCode() exitCode { Version: versionInfo.Version, ReportInterval: intervals.ReportInterval(), ExecutablesCacheElements: defaultExecutablesCacheSize, - // Next step: Calculate FramesCacheElements from numCores and samplingRate. - FramesCacheElements: defaultFramesCacheSize, - ProcessesCacheElements: defaultProcessesCacheSize, - SamplesPerSecond: int(args.samplesPerSecond), - PprofPrefix: args.pprofPrefix, - Tags: validatedTags, - Timeline: args.timeline, - APIKey: apiKey, - EnableSplitByService: args.enableSplitByService, - SplitServiceSuffix: args.splitServiceSuffix, - HostServiceName: args.hostServiceName, + ProcessesCacheElements: defaultProcessesCacheSize, + SamplesPerSecond: int(args.samplesPerSecond), + PprofPrefix: args.pprofPrefix, + Tags: validatedTags, + Timeline: args.timeline, + APIKey: apiKey, + EnableSplitByService: args.enableSplitByService, + SplitServiceSuffix: args.splitServiceSuffix, + HostServiceName: args.hostServiceName, SymbolUploaderConfig: reporter.SymbolUploaderConfig{ Enabled: args.uploadSymbols, UploadDynamicSymbols: args.uploadDynamicSymbols, @@ -276,7 +282,7 @@ func mainWithExitCode() exitCode { SamplesPerSecond: int(args.samplesPerSecond), MapScaleFactor: int(args.mapScaleFactor), KernelVersionCheck: !args.noKernelVersionCheck, - DebugTracer: args.verboseeBPF, + VerboseMode: args.verboseeBPF, BPFVerifierLogLevel: uint32(args.bpfVerifierLogLevel), ProbabilisticInterval: args.probabilisticInterval, ProbabilisticThreshold: uint(args.probabilisticThreshold), diff --git a/reporter/config.go b/reporter/config.go index 5b71d138..e985d687 100644 --- a/reporter/config.go +++ b/reporter/config.go @@ -30,8 +30,6 @@ type Config struct { IntakeURL string // ExecutablesCacheElements defines item capacity of the executables cache. ExecutablesCacheElements uint32 - // FramesCacheElements defines the item capacity of the frames cache. - FramesCacheElements uint32 // ProcessesCacheElements defines the item capacity of the processes cache. ProcessesCacheElements uint32 // samplesPerSecond defines the number of samples per second. diff --git a/reporter/datadog_reporter.go b/reporter/datadog_reporter.go index cf72a70d..4bed79ea 100644 --- a/reporter/datadog_reporter.go +++ b/reporter/datadog_reporter.go @@ -8,8 +8,8 @@ package reporter import ( "bytes" "context" + "errors" "fmt" - "maps" "os" "path" "runtime" @@ -24,12 +24,16 @@ import ( "go.opentelemetry.io/ebpf-profiler/libpf/xsync" "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/reporter/samples" + "go.opentelemetry.io/ebpf-profiler/support" "github.com/DataDog/dd-otel-host-profiler/containermetadata" + "github.com/DataDog/dd-otel-host-profiler/reporter/pprof" + rsamples "github.com/DataDog/dd-otel-host-profiler/reporter/samples" ) // Assert that we implement the full Reporter interface. var _ reporter.Reporter = (*DatadogReporter)(nil) +var errUnknownOrigin = errors.New("unknown trace origin") const ( unknownServiceStr = "unknown-service" @@ -37,7 +41,6 @@ const ( profilerName = "dd-otel-host-profiler" pidCacheUpdateInterval = 1 * time.Minute // pid cache items will be updated at most once per this interval pidCacheCleanupInterval = 5 * time.Minute // pid cache items for which metadata hasn't been updated in this interval will be removed - executableCacheLifetime = 1 * time.Hour // executable cache items will be removed if unused after this interval framesCacheLifetime = 1 * time.Hour // frames cache items will be removed if unused after this interval profileUploadWorkerCount = 5 @@ -46,77 +49,12 @@ const ( var ServiceNameEnvVars = []string{"DD_SERVICE", "OTEL_SERVICE_NAME"} -// execInfo enriches an executable with additional metadata. -type execInfo struct { - fileName string - gnuBuildID string - goBuildID string -} - -// sourceInfo allows mapping a frame to its source origin. -type sourceInfo struct { - lineNumber libpf.SourceLineno - functionOffset uint32 - functionName string - filePath string -} - -// funcInfo is a helper to construct profile.Function messages. -type funcInfo struct { - name string - fileName string -} - -// traceAndMetaKey is the deduplication key for samples. This **must always** -// contain all trace fields that aren't already part of the trace hash to ensure -// that we don't accidentally merge traces with different fields. -type traceAndMetaKey struct { - hash libpf.TraceHash - // comm and apmServiceName are provided by the eBPF programs - comm string - executablePath string - apmServiceName string - pid libpf.PID - tid libpf.PID -} - -// traceEvents holds known information about a trace. -type traceEvents struct { - files []libpf.FileID - linenos []libpf.AddressOrLineno - frameTypes []libpf.FrameType - mappingStarts []libpf.Address - mappingEnds []libpf.Address - mappingFileOffsets []uint64 - timestamps []uint64 // in nanoseconds -} - -type processMetadata struct { - updatedAt time.Time - executablePath string - containerMetadata containermetadata.ContainerMetadata - ddService string -} - type uploadProfileData struct { - start time.Time - end time.Time - profile *pprofile.Profile - containerID string - entityID string - tags Tags -} - -type serviceEntity struct { - service string - containerID string - entityID string - inferredService bool -} - -type profileStats struct { - totalSampleCount int - pidWithNoMetadata int + start time.Time + end time.Time + profile *pprofile.Profile + entityID string + tags Tags } // DatadogReporter receives and transforms information to be OTLP/profiles compliant. @@ -131,16 +69,13 @@ type DatadogReporter struct { // be duplicated in other places but not accessible for DatadogReporter. // executables stores metadata for executables. - executables *lru.SyncedLRU[libpf.FileID, execInfo] - - // frames maps frame information to its source location. - frames *lru.SyncedLRU[libpf.FileID, *xsync.RWMutex[map[libpf.AddressOrLineno]sourceInfo]] + executables *lru.SyncedLRU[libpf.FileID, rsamples.ExecInfo] // traceEvents stores reported trace events (trace metadata with frames and counts) - traceEvents xsync.RWMutex[map[traceAndMetaKey]*traceEvents] + traceEvents xsync.RWMutex[rsamples.TraceEventsTree] // processes stores the metadata associated to a PID. - processes *lru.SyncedLRU[libpf.PID, processMetadata] + processes *lru.SyncedLRU[libpf.PID, rsamples.ProcessMetadata] symbolUploader *DatadogSymbolUploader @@ -155,6 +90,9 @@ type DatadogReporter struct { // profileSeq is the sequence number of the profile (ie. number of profiles uploaded until now). profileSeq uint64 + // processAlreadyExitedCount is the number of processes that have already exited when attempting to collect their metadata. + processAlreadyExitedCount int + // intervalStart is the timestamp of the start of the current interval. intervalStart time.Time @@ -162,24 +100,13 @@ type DatadogReporter struct { } func NewDatadog(cfg *Config, p containermetadata.Provider) (*DatadogReporter, error) { - executables, err := lru.NewSynced[libpf.FileID, execInfo](cfg.ExecutablesCacheElements, libpf.FileID.Hash32) + executables, err := lru.NewSynced[libpf.FileID, rsamples.ExecInfo](cfg.ExecutablesCacheElements, libpf.FileID.Hash32) if err != nil { return nil, err } - executables.SetLifetime(executableCacheLifetime) + executables.SetLifetime(rsamples.ExecutableCacheLifetime) - frames, err := lru.NewSynced[libpf.FileID, - *xsync.RWMutex[map[libpf.AddressOrLineno]sourceInfo]](cfg.FramesCacheElements, libpf.FileID.Hash32) - if err != nil { - return nil, err - } - frames.SetLifetime(framesCacheLifetime) - // Using SetLifetime / GetAndRefresh on frames prevents useful information from being - // evicted from the cache. - // But it also means that active frames will never be evicted, and thus that their corresponding values - // (which are maps) may grow indefinitely (cf. https://github.com/open-telemetry/opentelemetry-ebpf-profiler/pull/260). - - processes, err := lru.NewSynced[libpf.PID, processMetadata](cfg.ProcessesCacheElements, libpf.PID.Hash32) + processes, err := lru.NewSynced[libpf.PID, rsamples.ProcessMetadata](cfg.ProcessesCacheElements, libpf.PID.Hash32) if err != nil { return nil, err } @@ -204,9 +131,8 @@ func NewDatadog(cfg *Config, p containermetadata.Provider) (*DatadogReporter, er stopSignal: make(chan libpf.Void), }, executables: executables, - frames: frames, containerMetadataProvider: p, - traceEvents: xsync.NewRWMutex(map[traceAndMetaKey]*traceEvents{}), + traceEvents: xsync.NewRWMutex(make(rsamples.TraceEventsTree)), processes: processes, symbolUploader: symbolUploader, tags: createTags(cfg.Tags, runtimeTag, cfg.Version, cfg.EnableSplitByService), @@ -218,36 +144,57 @@ func NewDatadog(cfg *Config, p containermetadata.Provider) (*DatadogReporter, er // ReportTraceEvent enqueues reported trace events for the Datadog reporter. func (r *DatadogReporter) ReportTraceEvent(trace *libpf.Trace, meta *samples.TraceEventMeta) error { - traceEventsMap := r.traceEvents.WLock() - defer r.traceEvents.WUnlock(&traceEventsMap) + if meta.Origin != support.TraceOriginSampling && meta.Origin != support.TraceOriginOffCPU { + // At the moment only on-CPU and off-CPU traces are reported. + return fmt.Errorf("skip reporting trace for %d origin: %w", meta.Origin, + errUnknownOrigin) + } + + pMeta, ok := r.processes.Get(meta.PID) + if !ok || time.Since(pMeta.UpdatedAt) > pidCacheUpdateInterval { + pMeta = r.addProcessMetadata(trace, meta) + } + + key := rsamples.TraceAndMetaKey{ + Hash: trace.Hash, + Comm: meta.Comm, + Pid: meta.PID, + Tid: meta.TID, + } + + eventsTree := r.traceEvents.WLock() + defer r.traceEvents.WUnlock(&eventsTree) + + serviceEntityKey := rsamples.ServiceEntity{ + Service: pMeta.Service, + EntityID: pMeta.ContainerMetadata.EntityID, + InferredService: pMeta.InferredService, + } - if pMeta, ok := r.processes.Get(meta.PID); !ok || time.Since(pMeta.updatedAt) > pidCacheUpdateInterval { - r.addProcessMetadata(meta) + perServiceEvents, exists := (*eventsTree)[serviceEntityKey] + if !exists { + perServiceEvents = make(map[libpf.Origin]rsamples.KeyToEventMapping) + (*eventsTree)[serviceEntityKey] = perServiceEvents } - key := traceAndMetaKey{ - hash: trace.Hash, - comm: meta.Comm, - executablePath: meta.ExecutablePath, - apmServiceName: meta.APMServiceName, - pid: meta.PID, - tid: meta.TID, + perOriginEvents, exists := perServiceEvents[meta.Origin] + if !exists { + perOriginEvents = make(rsamples.KeyToEventMapping) + perServiceEvents[meta.Origin] = perOriginEvents } - if tr, exists := (*traceEventsMap)[key]; exists { - tr.timestamps = append(tr.timestamps, uint64(meta.Timestamp)) - (*traceEventsMap)[key] = tr + if events, exists := perOriginEvents[key]; exists { + events.Timestamps = append(events.Timestamps, uint64(meta.Timestamp)) + events.OffTimes = append(events.OffTimes, meta.OffTime) + perOriginEvents[key] = events return nil } - (*traceEventsMap)[key] = &traceEvents{ - files: trace.Files, - linenos: trace.Linenos, - frameTypes: trace.FrameTypes, - mappingStarts: trace.MappingStart, - mappingEnds: trace.MappingEnd, - mappingFileOffsets: trace.MappingFileOffsets, - timestamps: []uint64{uint64(meta.Timestamp)}, + perOriginEvents[key] = &samples.TraceEvents{ + Frames: trace.Frames, + Timestamps: []uint64{uint64(meta.Timestamp)}, + OffTimes: []int64{meta.OffTime}, + EnvVars: meta.EnvVars, } return nil @@ -256,17 +203,17 @@ func (r *DatadogReporter) ReportTraceEvent(trace *libpf.Trace, meta *samples.Tra // ExecutableKnown returns true if the metadata of the Executable specified by fileID is // cached in the reporter. func (r *DatadogReporter) ExecutableKnown(fileID libpf.FileID) bool { - _, known := r.executables.GetAndRefresh(fileID, executableCacheLifetime) + _, known := r.executables.GetAndRefresh(fileID, rsamples.ExecutableCacheLifetime) return known } // ExecutableMetadata accepts a fileID with the corresponding filename // and caches this information. func (r *DatadogReporter) ExecutableMetadata(args *reporter.ExecutableMetadataArgs) { - r.executables.Add(args.FileID, execInfo{ - fileName: path.Base(args.FileName), - gnuBuildID: args.GnuBuildID, - goBuildID: args.GoBuildID, + r.executables.Add(args.FileID, rsamples.ExecInfo{ + FileName: path.Base(args.FileName), + GnuBuildID: args.GnuBuildID, + GoBuildID: args.GoBuildID, }) if r.symbolUploader != nil && args.Interp == libpf.Native { @@ -274,62 +221,6 @@ func (r *DatadogReporter) ExecutableMetadata(args *reporter.ExecutableMetadataAr } } -// FrameKnown returns true if the metadata of the Frame specified by frameID is -// cached in the reporter. -func (r *DatadogReporter) FrameKnown(frameID libpf.FrameID) bool { - known := false - if frameMapLock, exists := r.frames.GetAndRefresh(frameID.FileID(), framesCacheLifetime); exists { - frameMap := frameMapLock.RLock() - defer frameMapLock.RUnlock(&frameMap) - _, known = (*frameMap)[frameID.AddressOrLine()] - } - return known -} - -// FrameMetadata accepts metadata associated with a frame and caches this information. -func (r *DatadogReporter) FrameMetadata(args *reporter.FrameMetadataArgs) { - fileID := args.FrameID.FileID() - addressOrLine := args.FrameID.AddressOrLine() - - log.Debugf("FrameMetadata [%x] %v+%v at %v:%v", - fileID, args.FunctionName, args.FunctionOffset, - args.SourceFile, args.SourceLine) - - if frameMapLock, exists := r.frames.GetAndRefresh(fileID, framesCacheLifetime); exists { - frameMap := frameMapLock.WLock() - defer frameMapLock.WUnlock(&frameMap) - - sourceFile := args.SourceFile - - if sourceFile == "" { - // The new sourceFile may be empty, and we don't want to overwrite - // an existing filePath with it. - if s, exists := (*frameMap)[addressOrLine]; exists { - sourceFile = s.filePath - } - } - - (*frameMap)[addressOrLine] = sourceInfo{ - lineNumber: args.SourceLine, - filePath: sourceFile, - functionOffset: args.FunctionOffset, - functionName: args.FunctionName, - } - - return - } - - v := make(map[libpf.AddressOrLineno]sourceInfo) - v[addressOrLine] = sourceInfo{ - lineNumber: args.SourceLine, - filePath: args.SourceFile, - functionOffset: args.FunctionOffset, - functionName: args.FunctionName, - } - mu := xsync.NewRWMutex(v) - r.frames.Add(fileID, &mu) -} - // ReportHostMetadata is a NOP for DatadogReporter. func (r *DatadogReporter) ReportHostMetadata(_ map[string]string) {} @@ -360,7 +251,6 @@ func (r *DatadogReporter) Start(mainCtx context.Context) error { }, func() { // Allow the GC to purge expired entries to avoid memory leaks. r.executables.PurgeExpired() - r.frames.PurgeExpired() r.processes.PurgeExpired() }) @@ -426,158 +316,7 @@ func (r *DatadogReporter) reportProfile(ctx context.Context, data *uploadProfile return uploadProfiles(ctx, []profileData{{name: "cpu.pprof", data: b.Bytes()}}, data.start, data.end, r.config.IntakeURL, data.tags, r.config.Version, r.config.APIKey, - data.containerID, data.entityID, r.family) -} - -func (r *DatadogReporter) createProfile(hostSamples map[traceAndMetaKey]*traceEvents, start, end time.Time) (*pprofile.Profile, profileStats) { - numSamples := len(hostSamples) - - const unknownStr = "UNKNOWN" - - // funcMap is a temporary helper that will build the Function array - // in profile and make sure information is deduplicated. - funcMap := make(map[funcInfo]*pprofile.Function) - - samplingPeriod := 1000000000 / int64(r.config.SamplesPerSecond) - profile := &pprofile.Profile{ - SampleType: []*pprofile.ValueType{{Type: "cpu-samples", Unit: "count"}, - {Type: "cpu-time", Unit: "nanoseconds"}}, - Sample: make([]*pprofile.Sample, 0, numSamples), - PeriodType: &pprofile.ValueType{Type: "cpu-time", Unit: "nanoseconds"}, - Period: samplingPeriod, - DefaultSampleType: "cpu-time", - } - - fileIDtoMapping := make(map[libpf.FileID]*pprofile.Mapping) - totalSampleCount := 0 - pidsWithNoProcessMetadata := libpf.Set[libpf.PID]{} - for traceKey, traceInfo := range hostSamples { - sample := &pprofile.Sample{} - - // Walk every frame of the trace. - for i := range traceInfo.frameTypes { - loc := createPProfLocation(profile, uint64(traceInfo.linenos[i])) - - switch frameKind := traceInfo.frameTypes[i]; frameKind { - case libpf.NativeFrame: - // As native frames are resolved in the backend, we use Mapping to - // report these frames. - - if tmpMapping, exists := fileIDtoMapping[traceInfo.files[i]]; exists { - loc.Mapping = tmpMapping - } else { - executionInfo, exists := r.executables.GetAndRefresh(traceInfo.files[i], executableCacheLifetime) - - // Next step: Select a proper default value, - // if the name of the executable is not known yet. - var fileName = unknownStr - var buildID = traceInfo.files[i].StringNoQuotes() - if exists { - fileName = executionInfo.fileName - buildID = getBuildID(executionInfo.gnuBuildID, executionInfo.goBuildID, buildID) - } - - tmpMapping := createPprofMapping(profile, uint64(traceInfo.linenos[i]), - fileName, buildID) - fileIDtoMapping[traceInfo.files[i]] = tmpMapping - loc.Mapping = tmpMapping - } - line := pprofile.Line{Function: createPprofFunctionEntry(funcMap, profile, "", - loc.Mapping.File)} - loc.Line = append(loc.Line, line) - case libpf.AbortFrame: - // Next step: Figure out how the OTLP protocol - // could handle artificial frames, like AbortFrame, - // that are not originate from a native or interpreted - // program. - default: - // Store interpreted frame information as Line message: - line := pprofile.Line{} - - fileIDInfoLock, exists := r.frames.GetAndRefresh(traceInfo.files[i], framesCacheLifetime) - if !exists { - // At this point, we do not have enough information for the frame. - // Therefore, we report a dummy entry and use the interpreter as filename. - line.Function = createPprofFunctionEntry(funcMap, profile, - "UNREPORTED", frameKind.String()) - } else { - fileIDInfo := fileIDInfoLock.RLock() - if si, exists := (*fileIDInfo)[traceInfo.linenos[i]]; exists { - line.Line = int64(si.lineNumber) - line.Function = createPprofFunctionEntry(funcMap, profile, - si.functionName, si.filePath) - } else { - // At this point, we do not have enough information for the frame. - // Therefore, we report a dummy entry and use the interpreter as filename. - line.Function = createPprofFunctionEntry(funcMap, profile, - "UNRESOLVED", frameKind.String()) - } - fileIDInfoLock.RUnlock(&fileIDInfo) - } - loc.Line = append(loc.Line, line) - - // To be compliant with the protocol generate a dummy mapping entry. - loc.Mapping = getDummyMapping(fileIDtoMapping, profile, traceInfo.files[i]) - } - sample.Location = append(sample.Location, loc) - } - - processMeta, ok := r.processes.Get(traceKey.pid) - if !ok { - pidsWithNoProcessMetadata[traceKey.pid] = libpf.Void{} - } - execPath := getExecutablePath(&processMeta, &traceKey) - - // Check if the last frame is a kernel frame. - if len(traceInfo.frameTypes) > 0 && - traceInfo.frameTypes[len(traceInfo.frameTypes)-1] == libpf.KernelFrame { - // If the last frame is a kernel frame, we need to add a dummy - // location with the kernel as the function name. - execPath = "kernel" - } - baseExec := path.Base(execPath) - - if execPath != "" { - loc := createPProfLocation(profile, 0) - m := createPprofFunctionEntry(funcMap, profile, baseExec, execPath) - loc.Line = append(loc.Line, pprofile.Line{Function: m}) - sample.Location = append(sample.Location, loc) - } - - containerMetadata := containermetadata.ContainerMetadata{} - if !r.config.EnableSplitByService { - containerMetadata = processMeta.containerMetadata - } - if !r.config.Timeline { - count := int64(len(traceInfo.timestamps)) - labels := make(map[string][]string) - addTraceLabels(labels, traceKey, containerMetadata, baseExec, 0) - sample.Value = append(sample.Value, count, count*samplingPeriod) - sample.Label = labels - profile.Sample = append(profile.Sample, sample) - } else { - sample.Value = append(sample.Value, 1, samplingPeriod) - for _, ts := range traceInfo.timestamps { - sampleWithTimestamp := &pprofile.Sample{} - *sampleWithTimestamp = *sample - labels := make(map[string][]string) - addTraceLabels(labels, traceKey, processMeta.containerMetadata, baseExec, ts) - sampleWithTimestamp.Label = labels - profile.Sample = append(profile.Sample, sampleWithTimestamp) - } - } - totalSampleCount += len(traceInfo.timestamps) - } - - profile.DurationNanos = end.Sub(start).Nanoseconds() - profile.TimeNanos = start.UnixNano() - - profile = profile.Compact() - - return profile, profileStats{ - totalSampleCount: totalSampleCount, - pidWithNoMetadata: len(pidsWithNoProcessMetadata), - } + data.entityID, r.family) } // getPprofProfile returns a pprof profile containing all collected samples up to this moment. @@ -588,17 +327,22 @@ func (r *DatadogReporter) getPprofProfile() { profileSeq := r.profileSeq r.profileSeq++ + processAlreadyExitedCount := r.processAlreadyExitedCount + r.processAlreadyExitedCount = 0 + events := r.traceEvents.WLock() - hostSamples := maps.Clone(*events) - for key := range *events { - delete(*events, key) - } + reportedEvents := *events + newEvents := make(rsamples.TraceEventsTree) + *events = newEvents r.traceEvents.WUnlock(&events) - entityToSample := make(map[serviceEntity]map[traceAndMetaKey]*traceEvents) - if !r.config.EnableSplitByService { - profile, stats := r.createProfile(hostSamples, intervalStart, intervalEnd) + profileBuilder := pprof.NewProfileBuilder(intervalStart, intervalEnd, r.config.SamplesPerSecond, len(reportedEvents), r.config.Timeline, r.executables, r.processes) + + for _, events := range reportedEvents { + profileBuilder.AddEvents(events[support.TraceOriginSampling]) + } + profile, stats := profileBuilder.Build() tags := createTagsForProfile(r.tags, profileSeq, r.config.HostServiceName, false) r.profiles <- &uploadProfileData{ @@ -609,66 +353,29 @@ func (r *DatadogReporter) getPprofProfile() { } log.Infof("Tags: %v", tags) log.Infof("Reporting single profile #%d from %v to %v: %d samples, %d PIDs with no process metadata", - profileSeq, intervalStart.Format(time.RFC3339), intervalEnd.Format(time.RFC3339), stats.totalSampleCount, stats.pidWithNoMetadata) + profileSeq, intervalStart.Format(time.RFC3339), intervalEnd.Format(time.RFC3339), stats.TotalSampleCount, processAlreadyExitedCount) return } - for traceKey, traceInfo := range hostSamples { - processMeta, _ := r.processes.Get(traceKey.pid) - - service := processMeta.ddService - execPath := getExecutablePath(&processMeta, &traceKey) - inferredService := false - - if service == "" && execPath != "" && execPath != "/" { - service = path.Base(execPath) - inferredService = true - } - - if service == "" && len(traceInfo.frameTypes) > 0 && - traceInfo.frameTypes[len(traceInfo.frameTypes)-1] == libpf.KernelFrame { - service = "system" - } - - if service == "" { - service = unknownServiceStr - inferredService = true - } - - entity := serviceEntity{ - service: service + r.config.SplitServiceSuffix, - containerID: processMeta.containerMetadata.ContainerID, - entityID: processMeta.containerMetadata.EntityID, - inferredService: inferredService, - } - serviceSamples, exists := entityToSample[entity] - if !exists { - serviceSamples = make(map[traceAndMetaKey]*traceEvents) - entityToSample[entity] = serviceSamples - } - - serviceSamples[traceKey] = traceInfo - } - totalSampleCount := 0 - totalPIDsWithNoProcessMetadata := 0 - for e, s := range entityToSample { - profile, stats := r.createProfile(s, intervalStart, intervalEnd) - totalSampleCount += stats.totalSampleCount - totalPIDsWithNoProcessMetadata += stats.pidWithNoMetadata - tags := createTagsForProfile(r.tags, profileSeq, e.service, e.inferredService) + for e, perServiceEvents := range reportedEvents { + profileBuilder := pprof.NewProfileBuilder(intervalStart, intervalEnd, r.config.SamplesPerSecond, len(reportedEvents), r.config.Timeline, r.executables, r.processes) + + profileBuilder.AddEvents(perServiceEvents[support.TraceOriginSampling]) + profile, stats := profileBuilder.Build() + totalSampleCount += stats.TotalSampleCount + tags := createTagsForProfile(r.tags, profileSeq, e.Service, e.InferredService) r.profiles <- &uploadProfileData{ - profile: profile, - start: intervalStart, - end: intervalEnd, - containerID: e.containerID, - entityID: e.entityID, - tags: tags, + profile: profile, + start: intervalStart, + end: intervalEnd, + entityID: e.EntityID, + tags: tags, } - log.Debugf("Reporting profile for service %s: %d samples, %d PIDs with no process metadata, tags: %v", e.service, stats.totalSampleCount, stats.pidWithNoMetadata, tags) + log.Debugf("Reporting profile for service %s: %d samples, tags: %v", e.Service, stats.TotalSampleCount, tags) } log.Infof("Reporting %d profiles #%d from %v to %v: %d samples, %d PIDs with no process metadata", - len(entityToSample), profileSeq, intervalStart.Format(time.RFC3339), intervalEnd.Format(time.RFC3339), totalSampleCount, totalPIDsWithNoProcessMetadata) + len(reportedEvents), profileSeq, intervalStart.Format(time.RFC3339), intervalEnd.Format(time.RFC3339), totalSampleCount, processAlreadyExitedCount) } func createTags(userTags Tags, runtimeTag, version string, splitByServiceEnabled bool) Tags { @@ -707,141 +414,32 @@ func createTagsForProfile(tags Tags, profileSeq uint64, service string, inferred return newTags } -// createFunctionEntry adds a new function and returns its reference index. -func createPprofFunctionEntry(funcMap map[funcInfo]*pprofile.Function, - profile *pprofile.Profile, - name string, fileName string) *pprofile.Function { - key := funcInfo{ - name: name, - fileName: fileName, - } - if function, exists := funcMap[key]; exists { - return function - } - - idx := uint64(len(profile.Function)) + 1 - function := &pprofile.Function{ - ID: idx, - Name: name, - Filename: fileName, - } - profile.Function = append(profile.Function, function) - funcMap[key] = function - - return function -} - -func addTraceLabels(labels map[string][]string, i traceAndMetaKey, containerMetadata containermetadata.ContainerMetadata, - baseExec string, timestamp uint64) { - // The naming has an impact on the backend side, - // this is why we use "thread id", "thread name" and "process name" - if i.tid != 0 { - labels["thread id"] = append(labels["thread id"], fmt.Sprintf("%d", i.tid)) - } - - if i.comm != "" { - labels["thread name"] = append(labels["thread name"], i.comm) - } - - if baseExec != "" { - labels["process name"] = append(labels["process name"], baseExec) - } - - if containerMetadata.PodName != "" { - labels["pod_name"] = append(labels["pod_name"], containerMetadata.PodName) - } - - if containerMetadata.ContainerID != "" { - labels["container_id"] = append(labels["container_id"], containerMetadata.ContainerID) - } - - if containerMetadata.ContainerName != "" { - labels["container_name"] = append(labels["container_name"], containerMetadata.ContainerName) - } - - if i.apmServiceName != "" { - labels["apmServiceName"] = append(labels["apmServiceName"], i.apmServiceName) - } - - if i.pid != 0 { - labels["process_id"] = append(labels["process_id"], fmt.Sprintf("%d", i.pid)) - } - - if timestamp != 0 { - labels["end_timestamp_ns"] = append(labels["end_timestamp_ns"], strconv.FormatUint(timestamp, 10)) - } -} - -// getDummyMappingIndex inserts or looks up a dummy entry for interpreted FileIDs. -func getDummyMapping(fileIDtoMapping map[libpf.FileID]*pprofile.Mapping, - profile *pprofile.Profile, fileID libpf.FileID) *pprofile.Mapping { - if tmpMapping, exists := fileIDtoMapping[fileID]; exists { - return tmpMapping - } - - mapping := createPprofMapping(profile, 0, "DUMMY", fileID.StringNoQuotes()) - fileIDtoMapping[fileID] = mapping - - return mapping -} - -func createPProfLocation(profile *pprofile.Profile, - address uint64) *pprofile.Location { - idx := uint64(len(profile.Location)) + 1 - location := &pprofile.Location{ - ID: idx, - Address: address, - } - profile.Location = append(profile.Location, location) - return location -} - -func createPprofMapping(profile *pprofile.Profile, offset uint64, - fileName string, buildID string) *pprofile.Mapping { - idx := uint64(len(profile.Mapping)) + 1 - mapping := &pprofile.Mapping{ - ID: idx, - File: fileName, - Offset: offset, - BuildID: buildID, - } - profile.Mapping = append(profile.Mapping, mapping) - return mapping -} - -func getBuildID(gnuBuildID, goBuildID, fileHash string) string { - // When building Go binaries, Bazel will set the Go build ID to "redacted" to - // achieve deterministic builds. Since Go 1.24, the Gnu Build ID is inherited - // from the Go build ID - if the Go build ID is "redacted", the Gnu Build ID will - // be a hash of "redacted". In this case, we should use the file hash instead of build IDs. - if goBuildID == "redacted" { - return fileHash - } - if gnuBuildID != "" { - return gnuBuildID - } - if goBuildID != "" { - return goBuildID - } - return fileHash -} - -func (r *DatadogReporter) addProcessMetadata(meta *samples.TraceEventMeta) { +func (r *DatadogReporter) addProcessMetadata(trace *libpf.Trace, meta *samples.TraceEventMeta) rsamples.ProcessMetadata { pid := meta.PID execPath, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid)) if err != nil { + // Process might have exited since the trace was collected or process is a kernel thread. log.Debugf("Failed to get process metadata for PID %d: %v", pid, err) - return + execPath = meta.ExecutablePath + } + + var processName string + if name, err2 := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid)); err2 == nil { + processName = string(name) + } else { + r.processAlreadyExitedCount++ + processName = meta.ProcessName } - var ddService string + var service string var ok bool for _, envVarName := range ServiceNameEnvVars { - ddService, ok = meta.EnvVars[envVarName] + service, ok = meta.EnvVars[envVarName] if ok { break } } + // If DD_SERVICE is not set and the executable path is different from the one in the trace // (meaning the process has probably exec'd into another binary) // then attempt to retrieve again DD_SERVICE. @@ -849,35 +447,51 @@ func (r *DatadogReporter) addProcessMetadata(meta *samples.TraceEventMeta) { // (without the final container environment) that then execs into the final binary // with the container environment. if !ok && meta.ExecutablePath != execPath { - ddService = getServiceName(pid) + service = getServiceName(pid) } - containerMetadata, err := r.containerMetadataProvider.GetContainerMetadata(pid) - if err != nil { - log.Debugf("Failed to get container metadata for PID %d: %v", pid, err) - // Even upon failure, we might still have managed to get the containerID + inferredService := false + if service == "" && execPath != "" { + service = path.Base(execPath) + inferredService = true } - r.processes.Add(pid, processMetadata{ - updatedAt: time.Now(), - executablePath: execPath, - containerMetadata: containerMetadata, - ddService: ddService, - }) -} + if service == "" && rsamples.IsKernel(trace.Frames) { + service = "system" + } + + if service == "" { + service = unknownServiceStr + inferredService = true + } -func getExecutablePath(processMeta *processMetadata, traceKey *traceAndMetaKey) string { - if processMeta.executablePath != "" { - // If we were unable to get the process metadata, we use the executable path - // from the trace key. - // This can happen if the process has already exited when process metadata - // was collected. - // We prioritize the executable path from process metadata over the trace key - // because in some cases executable path from trace key is taken too early in - // the process lifetime, eg. before the process execs into another binary. - return processMeta.executablePath - } - return traceKey.executablePath + var containerMetadata containermetadata.ContainerMetadata + if meta.ContainerID != "" && r.config.EnableSplitByService { + // Use containerID when found by the eBPF profiler and not other container metadata is needed + // (ie. split by service is enabled). + // eBPF profiler only supports cgroup v2 and even with cgroup v2, depending on Kubernetes settings, + // containerID might not be available in /proc//cgroup. + containerMetadata = containermetadata.ContainerMetadata{ + EntityID: "ci-" + meta.ContainerID, + } + } else { + containerMetadata, err = r.containerMetadataProvider.GetContainerMetadata(pid) + if err != nil { + log.Debugf("Failed to get container metadata for PID %d: %v", pid, err) + // Even upon failure, we might still have managed to get the containerID + } + } + + pMeta := rsamples.ProcessMetadata{ + UpdatedAt: time.Now(), + ExecutablePath: execPath, + ProcessName: processName, + ContainerMetadata: containerMetadata, + Service: service, + InferredService: inferredService, + } + r.processes.Add(pid, pMeta) + return pMeta } func getServiceNameFromProcPath(pid libpf.PID, procRoot string) string { diff --git a/reporter/datadog_upload.go b/reporter/datadog_upload.go index a347855e..5d93fc51 100644 --- a/reporter/datadog_upload.go +++ b/reporter/datadog_upload.go @@ -14,6 +14,7 @@ import ( "mime/multipart" "net/http" "net/textproto" + "strings" "time" ) @@ -23,8 +24,7 @@ type profileData struct { } func uploadProfiles(ctx context.Context, profiles []profileData, startTime, endTime time.Time, - url string, tags Tags, profilerVersion string, apiKey string, containerID string, entityID string, - family string) error { + url string, tags Tags, profilerVersion string, apiKey string, entityID string, family string) error { contentType, body, err := buildMultipartForm(profiles, startTime, endTime, tags, family) if err != nil { return err @@ -41,11 +41,11 @@ func uploadProfiles(ctx context.Context, profiles []profileData, startTime, endT if apiKey != "" { req.Header.Set("Dd-Api-Key", apiKey) } - if containerID != "" { - req.Header.Set("Datadog-Container-Id", containerID) - } if entityID != "" { req.Header.Set("Datadog-Entity-Id", entityID) + if strings.HasPrefix(entityID, "ci-") { + req.Header.Set("Datadog-Container-Id", entityID[3:]) + } } resp, err := http.DefaultClient.Do(req) if err != nil { diff --git a/reporter/pprof/profile_builder.go b/reporter/pprof/profile_builder.go new file mode 100644 index 00000000..7ec59f1d --- /dev/null +++ b/reporter/pprof/profile_builder.go @@ -0,0 +1,285 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025 Datadog, Inc. + +package pprof + +import ( + "fmt" + "path" + "time" + + lru "github.com/elastic/go-freelru" + pprofile "github.com/google/pprof/profile" + "go.opentelemetry.io/ebpf-profiler/libpf" + + "github.com/DataDog/dd-otel-host-profiler/containermetadata" + samples "github.com/DataDog/dd-otel-host-profiler/reporter/samples" +) + +const unknownStr = "UNKNOWN" + +var dummyFileID = libpf.NewFileID(0, 0) + +type ProfileBuilder struct { + profile *pprofile.Profile + funcMap map[funcInfo]*pprofile.Function + fileIDtoMapping map[libpf.FileID]*pprofile.Mapping + executables *lru.SyncedLRU[libpf.FileID, samples.ExecInfo] + processes *lru.SyncedLRU[libpf.PID, samples.ProcessMetadata] + timeline bool + totalSampleCount int + pidsWithNoMetadata libpf.Set[libpf.PID] + samplingPeriod int64 +} + +type ProfileStats struct { + TotalSampleCount int +} + +func NewProfileBuilder(start, end time.Time, samplesPerSecond int, numSamples int, timeline bool, + executables *lru.SyncedLRU[libpf.FileID, samples.ExecInfo], processes *lru.SyncedLRU[libpf.PID, samples.ProcessMetadata]) *ProfileBuilder { + // funcMap is a temporary helper that will build the Function array + // in profile and make sure information is deduplicated. + funcMap := make(map[funcInfo]*pprofile.Function) + fileIDtoMapping := make(map[libpf.FileID]*pprofile.Mapping) + + samplingPeriod := 1000000000 / int64(samplesPerSecond) + profile := &pprofile.Profile{ + SampleType: []*pprofile.ValueType{{Type: "cpu-samples", Unit: "count"}, + {Type: "cpu-time", Unit: "nanoseconds"}}, + Sample: make([]*pprofile.Sample, 0, numSamples), + PeriodType: &pprofile.ValueType{Type: "cpu-time", Unit: "nanoseconds"}, + Period: samplingPeriod, + DefaultSampleType: "cpu-time", + } + profile.DurationNanos = end.Sub(start).Nanoseconds() + profile.TimeNanos = start.UnixNano() + + return &ProfileBuilder{ + profile: profile, + funcMap: funcMap, + fileIDtoMapping: fileIDtoMapping, + executables: executables, + processes: processes, + pidsWithNoMetadata: libpf.Set[libpf.PID]{}, + timeline: timeline, + samplingPeriod: samplingPeriod, + } +} + +func (b *ProfileBuilder) AddEvents(events samples.KeyToEventMapping) { + for traceKey, traceInfo := range events { + sample := &pprofile.Sample{} + + // Walk every frame of the trace. + for _, uniqueFrame := range traceInfo.Frames { + frame := uniqueFrame.Value() + loc := b.createPProfLocation(uint64(frame.AddressOrLineno)) + + switch frameKind := frame.Type; frameKind { + case libpf.NativeFrame: + // As native frames are resolved in the backend, we use Mapping to + // report these frames. + loc.Mapping = b.createPprofMappingForFileID(frame.FileID) + loc.Line = append(loc.Line, pprofile.Line{Function: b.createPprofFunctionEntry("", loc.Mapping.File)}) + case libpf.AbortFrame: + // Next step: Figure out how the OTLP protocol + // could handle artificial frames, like AbortFrame, + // that are not originate from a native or interpreted + // program. + default: + sourceFile := frame.SourceFile.String() + if frameKind == libpf.KernelFrame { + sourceFile = "kernel" + } + + // Store interpreted frame information as Line message: + line := pprofile.Line{ + Line: int64(frame.SourceLine), + Function: b.createPprofFunctionEntry(frame.FunctionName.String(), sourceFile), + } + + loc.Line = append(loc.Line, line) + loc.Mapping = b.getDummyMapping() + } + sample.Location = append(sample.Location, loc) + } + + processMeta, _ := b.processes.Get(traceKey.Pid) + execPath := processMeta.ExecutablePath + + var baseExec string + + switch { + case execPath != "": + baseExec = path.Base(execPath) + + case samples.IsKernel(traceInfo.Frames): + execPath = "kernel" + if processMeta.ProcessName != "" { + baseExec = processMeta.ProcessName + } else { + baseExec = execPath + } + + default: + execPath = traceKey.Comm + baseExec = execPath + } + + if execPath != "" { + loc := b.createPProfLocation(0) + m := b.createPprofFunctionEntry(baseExec, execPath) + loc.Line = append(loc.Line, pprofile.Line{Function: m}) + sample.Location = append(sample.Location, loc) + } + + var count int64 = 1 + if b.timeline { + count = int64(len(traceInfo.Timestamps)) + } + + labels := make(map[string][]string) + addTraceLabels(labels, traceKey, processMeta.ContainerMetadata, baseExec) + sample.Label = labels + sample.Value = append(sample.Value, count, count*b.samplingPeriod) + + if !b.timeline { + b.profile.Sample = append(b.profile.Sample, sample) + } else { + for _, ts := range traceInfo.Timestamps { + sampleWithTimestamp := &pprofile.Sample{} + *sampleWithTimestamp = *sample + sampleWithTimestamp.NumLabel = make(map[string][]int64) + sampleWithTimestamp.NumLabel["timestamp_ns"] = append(sampleWithTimestamp.NumLabel["timestamp_ns"], int64(ts)) + b.profile.Sample = append(b.profile.Sample, sampleWithTimestamp) + } + } + b.totalSampleCount += len(traceInfo.Timestamps) + } +} + +func (b *ProfileBuilder) Build() (*pprofile.Profile, ProfileStats) { + profile := b.profile.Compact() + stats := ProfileStats{ + TotalSampleCount: b.totalSampleCount, + } + return profile, stats +} + +// funcInfo is a helper to construct profile.Function messages. +type funcInfo struct { + name string + fileName string +} + +// createFunctionEntry adds a new function and returns its reference index. +func (b *ProfileBuilder) createPprofFunctionEntry(name, fileName string) *pprofile.Function { + key := funcInfo{ + name: name, + fileName: fileName, + } + if function, exists := b.funcMap[key]; exists { + return function + } + + idx := uint64(len(b.profile.Function)) + 1 + function := &pprofile.Function{ + ID: idx, + Name: name, + Filename: fileName, + } + b.profile.Function = append(b.profile.Function, function) + b.funcMap[key] = function + + return function +} + +// getDummyMappingIndex inserts or looks up a dummy entry for interpreted FileIDs. +func (b *ProfileBuilder) getDummyMapping() *pprofile.Mapping { + if tmpMapping, exists := b.fileIDtoMapping[dummyFileID]; exists { + return tmpMapping + } + + mapping := b.createPprofMapping("DUMMY", dummyFileID.StringNoQuotes()) + b.fileIDtoMapping[dummyFileID] = mapping + + return mapping +} + +func (b *ProfileBuilder) createPProfLocation(address uint64) *pprofile.Location { + idx := uint64(len(b.profile.Location)) + 1 + location := &pprofile.Location{ + ID: idx, + Address: address, + } + b.profile.Location = append(b.profile.Location, location) + return location +} + +func (b *ProfileBuilder) createPprofMappingForFileID(fileID libpf.FileID) *pprofile.Mapping { + if mapping, exists := b.fileIDtoMapping[fileID]; exists { + return mapping + } + + executionInfo, exists := b.executables.GetAndRefresh(fileID, samples.ExecutableCacheLifetime) + + fileName := unknownStr + buildID := fileID.StringNoQuotes() + if exists { + fileName = executionInfo.FileName + buildID = samples.GetBuildID(executionInfo.GnuBuildID, executionInfo.GoBuildID, buildID) + } + + mapping := b.createPprofMapping(fileName, buildID) + b.fileIDtoMapping[fileID] = mapping + return mapping +} + +func (b *ProfileBuilder) createPprofMapping(fileName, buildID string) *pprofile.Mapping { + idx := uint64(len(b.profile.Mapping)) + 1 + mapping := &pprofile.Mapping{ + ID: idx, + File: fileName, + Offset: 0, + BuildID: buildID, + } + b.profile.Mapping = append(b.profile.Mapping, mapping) + return mapping +} + +func addTraceLabels(labels map[string][]string, i samples.TraceAndMetaKey, containerMetadata containermetadata.ContainerMetadata, + processName string) { + // The naming has an impact on the backend side, + // this is why we use "thread id", "thread name" and "process name" + if i.Tid != 0 { + labels["thread id"] = append(labels["thread id"], fmt.Sprintf("%d", i.Tid)) + } + + if i.Pid != 0 { + labels["process_id"] = append(labels["process_id"], fmt.Sprintf("%d", i.Pid)) + } + + if i.Comm != "" { + labels["thread name"] = append(labels["thread name"], i.Comm) + } + + if processName != "" { + labels["process name"] = append(labels["process name"], processName) + } + + if containerMetadata.PodName != "" { + labels["pod_name"] = append(labels["pod_name"], containerMetadata.PodName) + } + + // In split by service, ContainerID always empty. + if containerMetadata.ContainerID != "" { + labels["container_id"] = append(labels["container_id"], containerMetadata.ContainerID) + } + + if containerMetadata.ContainerName != "" { + labels["container_name"] = append(labels["container_name"], containerMetadata.ContainerName) + } +} diff --git a/reporter/samples/samples.go b/reporter/samples/samples.go new file mode 100644 index 00000000..c6d3e6a4 --- /dev/null +++ b/reporter/samples/samples.go @@ -0,0 +1,79 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025 Datadog, Inc. + +package samples + +import ( + "time" + + "go.opentelemetry.io/ebpf-profiler/libpf" + "go.opentelemetry.io/ebpf-profiler/reporter/samples" + + "github.com/DataDog/dd-otel-host-profiler/containermetadata" +) + +const ExecutableCacheLifetime = 1 * time.Hour // executable cache items will be removed if unused after this interval + +// ExecInfo enriches an executable with additional metadata. +type ExecInfo struct { + FileName string + GnuBuildID string + GoBuildID string +} + +// TraceAndMetaKey is the deduplication key for samples. This **must always** +// contain all trace fields that aren't already part of the trace hash to ensure +// that we don't accidentally merge traces with different fields. +type TraceAndMetaKey struct { + Hash libpf.TraceHash + // Comm is provided by the eBPF programs + Comm string + Pid libpf.PID + Tid libpf.PID +} + +type ProcessMetadata struct { + UpdatedAt time.Time + ExecutablePath string + ProcessName string + ContainerMetadata containermetadata.ContainerMetadata + Service string + InferredService bool +} + +type ServiceEntity struct { + Service string + EntityID string + InferredService bool +} + +type TraceEventsTree map[ServiceEntity]map[libpf.Origin]KeyToEventMapping + +type KeyToEventMapping map[TraceAndMetaKey]*samples.TraceEvents + +func GetBuildID(gnuBuildID, goBuildID, fileHash string) string { + // When building Go binaries, Bazel will set the Go build ID to "redacted" to + // achieve deterministic builds. Since Go 1.24, the Gnu Build ID is inherited + // from the Go build ID - if the Go build ID is "redacted", the Gnu Build ID will + // be a hash of "redacted". In this case, we should use the file hash instead of build IDs. + if goBuildID == "redacted" { + return fileHash + } + if gnuBuildID != "" { + return gnuBuildID + } + if goBuildID != "" { + return goBuildID + } + return fileHash +} + +func IsKernel(frames libpf.Frames) bool { + if len(frames) == 0 { + return false + } + + return frames[len(frames)-1].Value().Type == libpf.KernelFrame +} diff --git a/reporter/symbol_query_batching.go b/reporter/symbol_query_batching.go index 20f926bf..16e8e0de 100644 --- a/reporter/symbol_query_batching.go +++ b/reporter/symbol_query_batching.go @@ -13,6 +13,7 @@ import ( log "github.com/sirupsen/logrus" + samples "github.com/DataDog/dd-otel-host-profiler/reporter/samples" "github.com/DataDog/dd-otel-host-profiler/reporter/symbol" ) @@ -92,7 +93,7 @@ func ExecuteSymbolQueryBatch(ctx context.Context, batch SymbolQueryBatch, querie continue } - buildID := getBuildID(e.GnuBuildID(), e.GoBuildID(), e.FileHash()) + buildID := samples.GetBuildID(e.GnuBuildID(), e.GoBuildID(), e.FileHash()) if buildID == "" { result.fillWithError(errors.New("empty buildID")) continue