diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 59b5213f2..3ffe4595d 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -10,11 +10,6 @@ ignorePaths: [ 'LICENSES/**', ], - gomod: { - managerFilePatterns: [ - '/(^|/)tools\\.mod$/', - ], - }, ocb: { managerFilePatterns: [ '/cmd/otelcol-ebpf-profiler/manifest.yaml$/', diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index aee84d562..f21267814 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -25,7 +25,7 @@ jobs: uses: ./.github/workflows/env - name: Initialize CodeQL - uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: languages: go @@ -33,7 +33,7 @@ jobs: run: make - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: category: "/language:Go" timeout-minutes: 10 diff --git a/.github/workflows/collector-tests.yml b/.github/workflows/collector-tests.yml index 9ec8ae132..6dfac262f 100644 --- a/.github/workflows/collector-tests.yml +++ b/.github/workflows/collector-tests.yml @@ -8,6 +8,7 @@ on: branches: ["**"] paths: - cmd/otelcol-ebpf-profiler/manifest.yaml + - .github/workflows/collector-tests.yml concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} @@ -26,11 +27,11 @@ jobs: - name: Set up environment uses: ./.github/workflows/env - name: Set up Go Stable - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: stable - name: Cache coredump modules - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: tools/coredump/modulecache key: coredumps-collector-${{ hashFiles('tools/coredump/testdata/*/*.json') }} @@ -44,7 +45,16 @@ jobs: - name: Setup replace statement run: | COLLECTOR_PATH=/tmp/opentelemetry-collector ./support/local-collector.sh + # Add replace directives to internal/tools for genproto and ebpf-profiler + go mod edit -modfile=internal/tools/go.mod -replace='google.golang.org/genproto@v0.0.0-20220519153652-3a47de7e79bd=google.golang.org/genproto@v0.0.0-20260226221140-a57be14db171' + go mod edit -modfile=internal/tools/go.mod -replace='google.golang.org/genproto/googleapis/rpc@v0.0.0-20220519153652-3a47de7e79bd=google.golang.org/genproto/googleapis/rpc@v0.0.0-20260226221140-a57be14db171' + go mod edit -modfile=internal/tools/go.mod -replace="go.opentelemetry.io/ebpf-profiler=$(pwd)" go mod tidy + go mod tidy -modfile=internal/tools/go.mod + # Manual run of go generate and go mod tidy to have a clean start for make test-junit + go generate ./... + go mod tidy + go mod tidy -modfile=internal/tools/go.mod - name: Tests run: make test-junit - name: Generate Issue @@ -68,7 +78,7 @@ jobs: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go Stable - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: stable - name: Clone Collector diff --git a/.github/workflows/env/action.yml b/.github/workflows/env/action.yml index 4a1789de3..1ac18e33b 100644 --- a/.github/workflows/env/action.yml +++ b/.github/workflows/env/action.yml @@ -19,7 +19,7 @@ runs: aarch64) command -v aarch64-linux-gnu-gcc >/dev/null || { sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu; } ;; esac - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache-dependency-path: go.sum diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index ac5354c5d..5b73209af 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: fossas/fossa-action@c414b9ad82eaad041e47a7cf62a4f02411f427a0 # v1.8.0 + - uses: fossas/fossa-action@ff70fe9fe17cbd2040648f1c45e8ec4e4884dcf3 # v1.9.0 with: api-key: ${{secrets.FOSSA_API_KEY}} team: OpenTelemetry diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index b03b67ef4..0e8d41275 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -33,7 +33,7 @@ jobs: # uploads of run results in SARIF format to the repository Actions tab. # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts - name: "Upload artifact" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif @@ -42,6 +42,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: sarif_file: results.sarif diff --git a/.github/workflows/push-docker-image.yml b/.github/workflows/push-docker-image.yml index b7d1c6708..dca26ba5a 100644 --- a/.github/workflows/push-docker-image.yml +++ b/.github/workflows/push-docker-image.yml @@ -18,7 +18,7 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Login to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -31,7 +31,7 @@ jobs: run: | echo "tag=$(date +%Y%m%d%H%M)" >> $GITHUB_OUTPUT - name: Build and push - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: push: true file: Dockerfile diff --git a/.github/workflows/unit-test-ebpf.yml b/.github/workflows/unit-test-ebpf.yml index cbe61d542..7cf888dd5 100644 --- a/.github/workflows/unit-test-ebpf.yml +++ b/.github/workflows/unit-test-ebpf.yml @@ -39,7 +39,7 @@ jobs: exit 1 fi - if: failure() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: binary-blobs path: support/ebpf/tracer.ebpf.* diff --git a/.github/workflows/unit-test-on-pull-request.yml b/.github/workflows/unit-test-on-pull-request.yml index c41f98985..8b712d61f 100644 --- a/.github/workflows/unit-test-on-pull-request.yml +++ b/.github/workflows/unit-test-on-pull-request.yml @@ -17,7 +17,7 @@ jobs: - name: Clone code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache-dependency-path: go.sum @@ -78,7 +78,7 @@ jobs: - name: Set up environment uses: ./.github/workflows/env - name: Cache coredump modules - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: tools/coredump/modulecache key: coredumps-${{ matrix.target_arch }}-${{ hashFiles('tools/coredump/testdata/*/*.json') }} @@ -105,7 +105,7 @@ jobs: - name: Prepare integration test binaries for qemu tests run: make integration-test-binaries TARGET_ARCH=${{ matrix.target_arch }} - name: Upload integration test binaries - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: integration-test-binaries-${{ matrix.target_arch }} path: support/*.test @@ -117,12 +117,12 @@ jobs: - name: Clone code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache-dependency-path: go.sum - name: Cache coredump modules - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: tools/coredump/modulecache key: coredumps-arm64-${{ hashFiles('tools/coredump/testdata/*/*.json') }} @@ -145,7 +145,6 @@ jobs: # https://github.com/cilium/ci-kernels/pkgs/container/ci-kernels/versions?filters%5Bversion_type%5D=tagged # AMD64 - - { target_arch: amd64, kernel: 5.4.276 } - { target_arch: amd64, kernel: 5.10.217 } - { target_arch: amd64, kernel: 5.15.159 } - { target_arch: amd64, kernel: 6.1.91 } diff --git a/.github/workflows/unit-test-rust.yml b/.github/workflows/unit-test-rust.yml index 3503aae61..e28f98a8f 100644 --- a/.github/workflows/unit-test-rust.yml +++ b/.github/workflows/unit-test-rust.yml @@ -30,7 +30,7 @@ jobs: - name: Clone code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rust - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable with: targets: ${{ matrix.rust_target }} toolchain: 1.88.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a2c6bc78..010fe205b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ slack channel for discussions and questions. ## Pre-requisites -- Linux (5.4+ for x86-64, 5.5+ for ARM64) with eBPF enabled (the profiler currently only runs on Linux) +- Linux (5.10+) with eBPF enabled (the profiler currently only runs on Linux) - Go as specified in [go.mod](https://github.com/open-telemetry/opentelemetry-ebpf-profiler/blob/main/go.mod) - docker - Rust as specified in [Cargo.toml](https://github.com/open-telemetry/opentelemetry-ebpf-profiler/blob/main/Cargo.toml) @@ -86,6 +86,25 @@ All pull requests are squashed to a single commit upon merge to `main`. [`draft`](https://github.blog/2019-02-14-introducing-draft-pull-requests/). * Make sure CLA is signed and CI is clear. +### How to Address Review Feedback + +Please use the GitHub UI to accept review suggestions that you don't +subsequently modify. Do not reimplement them in a separate commit. +The latter behavior is problematic as it: + +1. Increases burden on reviewers who have to spend additional time to check + that your reimplementation accurately reflects the original suggestion. +2. Increases the probability of bugs being introduced into the codebase. + This is not a theoretical concern as we've seen it happen multiple times. +3. Does not credit the reviewer who came up with the accepted suggestion. + +You can pull the GitHub-generated commits (after you've accepted a suggestion) +into your local repository by executing: + +```sh +git pull +``` + ### How to Get PRs Merged A PR is considered **ready to merge** when: diff --git a/Cargo.lock b/Cargo.lock index 39954e9a8..0b566e729 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,17 +31,17 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] @@ -52,9 +52,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -70,13 +70,19 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "cpp_demangle" version = "0.5.1" @@ -88,9 +94,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.17" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ "libc", ] @@ -106,21 +112,21 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ - "generic-array", - "typenum", + "hybrid-array", ] [[package]] name = "digest" -version = "0.10.7" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer", + "const-oid", "crypto-common", ] @@ -154,9 +160,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" @@ -186,16 +192,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.3.4" @@ -242,9 +238,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -252,6 +248,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "id-arena" version = "2.3.0" @@ -260,12 +265,12 @@ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -312,9 +317,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "linux-raw-sys" @@ -330,9 +335,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.16.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" [[package]] name = "memchr" @@ -367,9 +372,9 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "object" -version = "0.38.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271638cd5fa9cca89c4c304675ca658efc4e64a66c716b7cfe1afb4b9611dbbc" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" dependencies = [ "memchr", ] @@ -393,9 +398,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "prettyplease" @@ -538,9 +543,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -586,9 +591,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.9" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures", @@ -603,9 +608,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "smallvec" @@ -699,9 +704,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-ident" @@ -715,19 +720,13 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -736,7 +735,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -797,6 +796,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 89510512c..83914fc56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ prost = "0.14.0" prost-build = "0.14.0" rustc-demangle = "0.1" serde_json = "1" -sha2 = "0.10" +sha2 = "0.11" tempfile = "3" thiserror = "2" zstd = "0.13.0" @@ -63,11 +63,11 @@ default-features = false features = ["std"] [workspace.dependencies.lru] -version = "0.16.0" +version = "0.18.0" default-features = false [workspace.dependencies.object] -version = "0.38.0" +version = "0.39.0" default-features = false features = ["std", "read_core", "elf", "macho", "unaligned"] diff --git a/LICENSES/github.com/hashicorp/go-version/.github/workflows/go-tests.yml b/LICENSES/github.com/hashicorp/go-version/.github/workflows/go-tests.yml index 34a4771ab..5c199c722 100644 --- a/LICENSES/github.com/hashicorp/go-version/.github/workflows/go-tests.yml +++ b/LICENSES/github.com/hashicorp/go-version/.github/workflows/go-tests.yml @@ -1,6 +1,8 @@ name: go-tests -on: [push] +on: + pull_request: + branches: [ main ] env: TEST_RESULTS: /tmp/test-results @@ -11,16 +13,16 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: [ 1.15.3, 1.19 ] + go-version: ['stable', 'oldstable'] steps: - name: Setup go - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Create test directory run: | @@ -30,7 +32,7 @@ jobs: run: go mod download - name: Cache / restore go modules - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: | ~/go/pkg/mod @@ -50,7 +52,7 @@ jobs: fi - name: Run golangci-lint - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 + uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # Install gotestsum with go get for 1.15.3; otherwise default to go install - name: Install gotestsum @@ -71,13 +73,13 @@ jobs: # Save coverage report parts - name: Upload and save artifacts - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: name: Test Results-${{matrix.go-version}} path: ${{ env.TEST_RESULTS }} - name: Upload coverage report - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: path: coverage.out name: Coverage-report-${{matrix.go-version}} diff --git a/LICENSES/github.com/hashicorp/go-version/CHANGELOG.md b/LICENSES/github.com/hashicorp/go-version/CHANGELOG.md index 6d48174bf..81b423151 100644 --- a/LICENSES/github.com/hashicorp/go-version/CHANGELOG.md +++ b/LICENSES/github.com/hashicorp/go-version/CHANGELOG.md @@ -1,3 +1,41 @@ +# 1.9.0 (Mar 30, 2026) + +ENHANCEMENTS: + +Support parsing versions with custom prefixes via opt-in option in https://github.com/hashicorp/go-version/pull/79 + +INTERNAL: + +- Bump the github-actions-backward-compatible group across 1 directory with 2 updates in https://github.com/hashicorp/go-version/pull/179 +- Bump the github-actions-breaking group with 4 updates in https://github.com/hashicorp/go-version/pull/180 +- Bump the github-actions-backward-compatible group with 3 updates in https://github.com/hashicorp/go-version/pull/182 +- Update GitHub Actions to trigger on pull requests and update go version in https://github.com/hashicorp/go-version/pull/185 +- Bump actions/upload-artifact from 6.0.0 to 7.0.0 in the github-actions-breaking group across 1 directory in https://github.com/hashicorp/go-version/pull/183 +- Bump the github-actions-backward-compatible group across 1 directory with 2 updates in https://github.com/hashicorp/go-version/pull/186 + +# 1.8.0 (Nov 28, 2025) + +ENHANCEMENTS: + +- Add benchmark test for version.String() in https://github.com/hashicorp/go-version/pull/159 +- Bytes implementation in https://github.com/hashicorp/go-version/pull/161 + +INTERNAL: + +- Add CODEOWNERS file in .github/CODEOWNERS in https://github.com/hashicorp/go-version/pull/145 +- Linting in https://github.com/hashicorp/go-version/pull/151 +- Correct typos in comments in https://github.com/hashicorp/go-version/pull/134 +- Migrate GitHub Actions updates from TSCCR to Dependabot in https://github.com/hashicorp/go-version/pull/155 +- Bump the github-actions-backward-compatible group with 2 updates in https://github.com/hashicorp/go-version/pull/157 +- Update doc reference in README in https://github.com/hashicorp/go-version/pull/135 +- Bump the github-actions-breaking group with 3 updates in https://github.com/hashicorp/go-version/pull/156 +- [Compliance] - PR Template Changes Required in https://github.com/hashicorp/go-version/pull/158 +- Bump actions/cache from 4.2.3 to 4.2.4 in the github-actions-backward-compatible group in https://github.com/hashicorp/go-version/pull/167 +- Bump actions/checkout from 4.2.2 to 5.0.0 in the github-actions-breaking group in https://github.com/hashicorp/go-version/pull/166 +- Bump the github-actions-breaking group across 1 directory with 2 updates in https://github.com/hashicorp/go-version/pull/171 +- [IND-4226] [COMPLIANCE] Update Copyright Headers in https://github.com/hashicorp/go-version/pull/172 +- drop init() in https://github.com/hashicorp/go-version/pull/175 + # 1.7.0 (May 24, 2024) ENHANCEMENTS: diff --git a/LICENSES/github.com/hashicorp/go-version/README.md b/LICENSES/github.com/hashicorp/go-version/README.md index 83a8249f7..552896021 100644 --- a/LICENSES/github.com/hashicorp/go-version/README.md +++ b/LICENSES/github.com/hashicorp/go-version/README.md @@ -34,6 +34,32 @@ if v1.LessThan(v2) { } ``` +#### Version Parsing and Comparison with Prefixes + +The library also supports parsing versions with a custom prefix. +Using the `WithPrefix` option, you can specify a prefix to strip +before parsing the version. + +Use `WithPrefix` when your input strings carry a known release prefix such as +`deployment-`, `controller-`, etc. + +After parsing, the prefix is not part of the canonical version value. This +means the regular comparison methods such as `Compare`, `LessThan`, `Equal`, +and `GreaterThan` compare only the stripped version. If you compare versions +from different prefixes with these methods, the prefixes are ignored. If you +need to reject cross-prefix comparisons, inspect the parsed prefixes before +comparing the versions. + +```go +v1, _ := version.NewVersion("deployment-v1.2.3-beta+metadata", version.WithPrefix("deployment-")) +v2, _ := version.NewVersion("deployment-v1.2.4", version.WithPrefix("deployment-")) + +if v1.LessThan(v2) { + fmt.Printf("%s (%s) is less than %s (%s)\n", v1, v1.Original(), v2, v2.Original()) + // Outputs: 1.2.3-beta+metadata (deployment-v1.2.3-beta+metadata) is less than 1.2.4 (deployment-v1.2.4) +} +``` + #### Version Constraints ```go diff --git a/LICENSES/github.com/hashicorp/go-version/version.go b/LICENSES/github.com/hashicorp/go-version/version.go index 17b29732e..b95503d3c 100644 --- a/LICENSES/github.com/hashicorp/go-version/version.go +++ b/LICENSES/github.com/hashicorp/go-version/version.go @@ -49,6 +49,23 @@ const ( `?` ) +// Optional options for NewVersion function. +type options struct { + // If set, this prefix will be trimmed from the version string before parsing. + prefix string +} + +// Option is a functional option for NewVersion. +type Option func(*options) + +// WithPrefix is a functional option that sets a prefix to be removed from the +// version string before parsing. +func WithPrefix(prefix string) Option { + return func(o *options) { + o.prefix = prefix + } +} + // Version represents a single version. type Version struct { metadata string @@ -56,12 +73,36 @@ type Version struct { segments []int64 si int original string + prefix string } -// NewVersion parses the given version and returns a new -// Version. -func NewVersion(v string) (*Version, error) { - return newVersion(v, getVersionRegexp()) +// NewVersion parses the given version and returns a new Version. +// +// Optional parsing behavior can be enabled with Option values such as +// WithPrefix, which validates and strips an expected prefix before parsing. +func NewVersion(v string, opts ...Option) (*Version, error) { + options := &options{} + for _, opt := range opts { + if opt != nil { + opt(options) + } + } + + vToParse := v + if options.prefix != "" { + if !strings.HasPrefix(v, options.prefix) { + return nil, fmt.Errorf("version %q does not have prefix %q", v, options.prefix) + } + vToParse = strings.TrimPrefix(v, options.prefix) + } + + ver, err := newVersion(vToParse, getVersionRegexp()) + if err != nil { + return nil, err + } + ver.prefix = options.prefix + ver.original = v + return ver, nil } // NewSemver parses the given version and returns a new @@ -424,6 +465,11 @@ func (v *Version) Original() string { return v.original } +// Prefix returns the explicit prefix used with WithPrefix, if any. +func (v *Version) Prefix() string { + return v.prefix +} + // UnmarshalText implements encoding.TextUnmarshaler interface. func (v *Version) UnmarshalText(b []byte) error { temp, err := NewVersion(string(b)) diff --git a/LICENSES/github.com/hashicorp/go-version/version_test.go b/LICENSES/github.com/hashicorp/go-version/version_test.go index 15a062324..8da634559 100644 --- a/LICENSES/github.com/hashicorp/go-version/version_test.go +++ b/LICENSES/github.com/hashicorp/go-version/version_test.go @@ -39,6 +39,8 @@ func TestNewVersion(t *testing.T) { {"1.7rc2", false}, {"v1.7rc2", false}, {"1.0-", false}, + {"controller-v0.40.2", true}, + {"azure-cli-v1.4.2", true}, } for _, tc := range cases { @@ -51,6 +53,33 @@ func TestNewVersion(t *testing.T) { } } +func TestNewVersionWithPrefix(t *testing.T) { + cases := []struct { + version string + prefix string + err bool + }{ + {"", "release-", true}, + {"rel-1.2.3", "release-", true}, + {"release_1.2.3", "release-", true}, + {"release_1.2.0-x.Y.0+metadata", "release_", false}, + {"release-1.2.0-x.Y.0+metadata-width-hyphen", "release-", false}, + {"myrelease-1.2.3-rc1-with-hyphen", "myrelease-", false}, + {"prefix-1.2.3.4", "prefix-", false}, + {"controller-v0.40.2", "controller-", false}, + {"azure-cli-v1.4.2", "azure-cli-", false}, + } + + for _, tc := range cases { + _, err := NewVersion(tc.version, WithPrefix(tc.prefix)) + if tc.err && err == nil { + t.Fatalf("expected error for version: %q", tc.version) + } else if !tc.err && err != nil { + t.Fatalf("error for version %q: %s", tc.version, err) + } + } +} + func TestNewSemver(t *testing.T) { cases := []struct { version string @@ -80,6 +109,8 @@ func TestNewSemver(t *testing.T) { {"1.7rc2", true}, {"v1.7rc2", true}, {"1.0-", true}, + {"controller-v0.40.2", true}, + {"azure-cli-v1.4.2", true}, } for _, tc := range cases { @@ -171,6 +202,107 @@ func TestVersionCompare(t *testing.T) { } } +func TestVersionCompareWithPrefix(t *testing.T) { + cases := []struct { + v1 string + v1Prefix string + v2 string + v2Prefix string + expected int + }{ + {"controller-v0.40.2", "controller-", "controller-v0.40.3", "controller-", -1}, + {"0.40.4", "", "controller-v0.40.2", "controller-", 1}, + {"0.40.4", "", "controller-v0.40.4", "controller-", 0}, + {"azure-cli-v1.4.2", "azure-cli-", "azure-cli-v1.4.2", "azure-cli-", 0}, + {"azure-cli-v1.4.1", "azure-cli-", "azure-cli-v1.4.2", "azure-cli-", -1}, + {"1.4.3", "", "azure-cli-v1.4.2", "azure-cli-", 1}, + {"v1.4.3", "", "azure-cli-v1.4.2", "azure-cli-", 1}, + {"controller-v1.4.1", "controller-", "azure-cli-v1.4.2", "azure-cli-", -1}, + } + + for _, tc := range cases { + var v1 *Version + var err error + if tc.v1Prefix != "" { + v1, err = NewVersion(tc.v1, WithPrefix(tc.v1Prefix)) + } else { + v1, err = NewVersion(tc.v1) + } + if err != nil { + t.Fatalf("err: %s", err) + } + + var v2 *Version + if tc.v2Prefix != "" { + v2, err = NewVersion(tc.v2, WithPrefix(tc.v2Prefix)) + } else { + v2, err = NewVersion(tc.v2) + } + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := v1.Compare(v2) + expected := tc.expected + if actual != expected { + t.Fatalf( + "%s <=> %s\nexpected: %d\nactual: %d", + tc.v1, tc.v2, + expected, actual) + } + } +} + +func TestVersionAccessorsWithPrefix(t *testing.T) { + v, err := NewVersion("controller-v1.2.0-beta.2+build.5", WithPrefix("controller-")) + if err != nil { + t.Fatalf("err: %s", err) + } + + if got := v.Prefix(); got != "controller-" { + t.Fatalf("expected prefix %q, got %q", "controller-", got) + } + + if got := v.Original(); got != "controller-v1.2.0-beta.2+build.5" { + t.Fatalf("expected original %q, got %q", "controller-v1.2.0-beta.2+build.5", got) + } + + if got := v.String(); got != "1.2.0-beta.2+build.5" { + t.Fatalf("expected string %q, got %q", "1.2.0-beta.2+build.5", got) + } + + if got := v.Metadata(); got != "build.5" { + t.Fatalf("expected metadata %q, got %q", "build.5", got) + } + + if got := v.Prerelease(); got != "beta.2" { + t.Fatalf("expected prerelease %q, got %q", "beta.2", got) + } + + expectedSegments := []int{1, 2, 0} + if got := v.Segments(); !reflect.DeepEqual(got, expectedSegments) { + t.Fatalf("expected segments %#v, got %#v", expectedSegments, got) + } + + expectedSegments64 := []int64{1, 2, 0} + if got := v.Segments64(); !reflect.DeepEqual(got, expectedSegments64) { + t.Fatalf("expected segments64 %#v, got %#v", expectedSegments64, got) + } +} + +func TestVersionSegmentsWithPrefix(t *testing.T) { + v, err := NewVersion("azure-cli-v1.4.2", WithPrefix("azure-cli-")) + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := []int{1, 4, 2} + actual := v.Segments() + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected: %#v\nactual: %#v", expected, actual) + } +} + func TestVersionCompare_versionAndSemver(t *testing.T) { cases := []struct { versionRaw string diff --git a/LICENSES/go.opentelemetry.io/proto/otlp/LICENSE b/LICENSES/go.opentelemetry.io/proto/otlp/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSES/go.opentelemetry.io/proto/otlp/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Makefile b/Makefile index 02c17e260..729c62299 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ GO_TAGS := osusergo,netgo EBPF_FLAGS := GO_FLAGS := -buildvcs=false -ldflags="$(LDFLAGS)" -GO_TOOLS := -modfile=tools.mod +GO_TOOLS := -modfile=internal/tools/go.mod MAKEFLAGS += -j$(shell nproc) @@ -83,7 +83,7 @@ ebpf-profiler: ebpf go build $(GO_FLAGS) -tags $(GO_TAGS) otelcol-ebpf-profiler: ebpf generate-collector - cd cmd/otelcol-ebpf-profiler/ && go build $(GO_FLAGS) -tags "$(GO_TAGS)" -o ../../$@ + cd cmd/otelcol-ebpf-profiler/ && go build $(GO_FLAGS) -tags "$(GO_TAGS)" -o ../../$@ # Sets opentelemetry collector modules to be pulled from local source tree. # This command allows you to make changes to your local checkout of otel core and build @@ -94,7 +94,7 @@ otelcol-ebpf-profiler: ebpf generate-collector # 2. Run `make otel-from-tree` (only need to run it once to remap go modules) # 3. You can now build collector and it will use your local otel core changes. # 4. Before committing/pushing your changes, undo by running `make otel-from-lib`. -otel-from-tree: +otel-from-tree: generate-collector ./cmd/otelcol-ebpf-profiler/otel-from-tree.sh # Removes local opentelemetry-collector replaces from manifest.yaml. @@ -128,7 +128,7 @@ format-ebpf: vanity-import-check: go tool $(GO_TOOLS) porto --skip-dirs "^(LICENSES|go|target).*" --include-internal -l . || ( echo "(run: make vanity-import-fix)"; exit 1 ) -vanity-import-fix: $(PORTO) +vanity-import-fix: go tool $(GO_TOOLS) porto --skip-dirs "^(LICENSES|go|target).*" --include-internal -w . test: generate ebpf test-deps @@ -149,7 +149,7 @@ test-deps: ($(MAKE) -C "$(testdata_dir)") || exit ; \ ) -TEST_INTEGRATION_BINARY_DIRS := tracer processmanager/ebpf support interpreter/golabels/integrationtests +TEST_INTEGRATION_BINARY_DIRS := tracer processmanager/ebpf kallsyms support interpreter/golabels/integrationtests pprof-execs: pprof_1_23 pprof_1_24 pprof_1_24_cgo pprof_1_24_cgo_pie pprof_stable pprof_stable_cgo pprof_stable_cgo_pie diff --git a/README.md b/README.md index 93e9332c7..b7149f6b4 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@ eBPF. ## Core features and strengths -- Implements the [experimental OTel profiling - signal](https://github.com/open-telemetry/opentelemetry-proto/pull/534) +- Implements the [Alpha OTel Profiles signal](https://github.com/open-telemetry/opentelemetry-proto/pull/775) - Very low CPU and memory overhead (1% CPU and 250MB memory are our upper limits in testing and the agent typically manages to stay way below that) - Support for native C/C++ executables without the need for DWARF debug @@ -19,22 +18,25 @@ eBPF. languages. - Support for native code (C/C++, Rust, Zig, Go, etc. without debug symbols on host) -- Support for a broad set of HLLs, like Hotspot JVM, Python, Ruby, PHP, Node.JS, V8, - Perl, Erlang and .NET. +- Support for a broad set of HLLs, like Hotspot JVM, Python, Ruby, PHP, Node.JS, + V8, Perl, Erlang and .NET. - 100% non-intrusive: there's no need to load agents or libraries into the processes that are being profiled. - No need for any reconfiguration, instrumentation or restarts of HLL interpreters and VMs: the agent supports unwinding each of the supported languages in the default configuration. -- ARM64 support for all unwinders except NodeJS. +- ARM64 support for all unwinders except .NET. - Support for native `inline frames`, which provide insights into compiler optimizations and offer a higher precision of function call chains. ## Building -We are working towards integrating the profiling functionality into the [OTel Collector](https://opentelemetry.io/docs/collector/) as a receiver, -which will be the supported configuration going forward. In the meantime, we also offer a standalone profiling agent binary named `ebpf-profiler`, -to aid with development and debugging. The expectation is that this will go away once the integration with the [OTel Collector](https://opentelemetry.io/docs/collector/) is complete. +We have integrated the profiler into the [OTel Collector](https://opentelemetry.io/docs/collector/) as a receiver, +and this is the [supported configuration](https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-ebpf-profiler) going forward. + +To aid with development, testing and debugging, we also offer a standalone profiling agent binary named `ebpf-profiler`, +and a local build of an OTel Collector profiling receiver binary (`otelcol-ebpf-profiler`). These binaries are not +supported in any way, can be dropped in the future and should not be deployed in production. ## Platform Requirements The agent can be built with the provided make targets. Docker is required for containerized builds, and both amd64 and arm64 architectures are supported. @@ -56,7 +58,16 @@ Since the profiler is Linux-only, macOS and Windows users need to set up a Linux ## Supported Linux kernel version -[7ddc23ea](https://github.com/open-telemetry/opentelemetry-ebpf-profiler/commit/7ddc23ea135a2e00fffc17850ab90534e9b63108) is the last commit with support for 4.19. Changes after this commit may require a minimal Linux kernel version of 5.4. +The minimum required Linux kernel version has increased with certain commits. Specifically: + +- Commit [8047150e](https://github.com/open-telemetry/opentelemetry-ebpf-profiler/commit/8047150e3f325f852874591356c69d0487b67d7c) was the last to support kernel version 5.4. Subsequent changes may require a minimal Linux kernel version of 5.10 or greater. +- Commit [7ddc23ea](https://github.com/open-telemetry/opentelemetry-ebpf-profiler/commit/7ddc23ea135a2e00fffc17850ab90534e9b63108) was the last to support kernel version 4.19. Subsequent changes may require a minimal Linux kernel version of at least 5.4. + +### Updating the supported Linux kernel version + +The project maintains its minimum supported kernel version in line with the lowest kernel version currently provided by actively maintained major Linux distributions, which include Debian stable, Red Hat Enterprise Linux, Ubuntu LTS, Amazon Linux and SUSE Linux. The minimum requirement may be increased when all such distributions no longer ship a specific kernel version. This approach enables the codebase to utilize newer eBPF features and avoids the need to maintain compatibility shims for obsolete kernels. + +It should be noted that certain distributions incorporate eBPF features from newer kernels into their supported versions. When this occurs, the distribution's stated kernel version does not accurately reflect its true eBPF capabilities and will not prevent us from increasing the minimum supported version. On such kernels, the `no-kernel-version-check` configuration option can be used to bypass the checks and allow the profiler to execute. ## Alternative Build (Without Docker) You can build the agent without Docker by directly installing the dependencies listed in the Dockerfile. Once dependencies are set up, simply run: @@ -69,6 +80,18 @@ make debug ``` This will build the profiler natively on your machine. +## Building `otelcol-ebpf-profiler` locally (Without Docker) +You can build the local `otelcol-ebpf-profiler` binary by running: +```sh +make otelcol-ebpf-profiler +``` +or to cross-compile for a different architecture (e.g. arm64): +```sh +make otelcol-ebpf-profiler TARGET_ARCH=arm64 +``` + +See [local.example.yml](https://github.com/open-telemetry/opentelemetry-ebpf-profiler/blob/main/cmd/otelcol-ebpf-profiler/local.example.yaml) for an example configuration. + ## Running You can start the agent with the following command: @@ -77,17 +100,34 @@ You can start the agent with the following command: sudo ./ebpf-profiler -collection-agent=127.0.0.1:11000 -disable-tls ``` +To start the OTel Collector profiling receiver, run: +```sh +sudo ./otelcol-ebpf-profiler --feature-gates=+service.profilesSupport --config cmd/otelcol-ebpf-profiler/local.example.yaml +``` + The agent comes with a functional but work-in-progress / evolving implementation -of the recently released OTel profiling [signal](https://github.com/open-telemetry/opentelemetry-proto/pull/534). +of the recently released Alpha OTel Profiles [signal](https://github.com/open-telemetry/opentelemetry-proto/pull/775). The agent loads the eBPF program and its maps, starts unwinding and reports captured traces to the backend. +## Open Source Backends +As the OTel Profiles signal is still in development, mature production-ready +backends have yet to emerge. The following open source projects can be used as backends: + +- [devfiler](https://github.com/elastic/devfiler) — to speed up development and + experimentation, Elastic has open-sourced a desktop application that + reimplements the backend (collection, data storage, symbolization and UI) + portion of the eBPF profiler. Note that devfiler is not a real production + backend and should not be used as such. It is solely aimed at testing, + experimentation and development. +- [Pyroscope](https://github.com/grafana/pyroscope) — an open source continuous + profiling database that natively supports ingesting OTel profiling data. + ## Development To understand how this project works and learn more about profiling, check out [Profiling internals](doc/internals.md) - # Legal ## Licensing Information @@ -98,6 +138,15 @@ This project is licensed under the Apache License 2.0 (Apache-2.0). The eBPF source code is licensed under the GPL 2.0 license. [GPL 2.0](support/ebpf/LICENSE) +### Emeritus + +- [Dmitry Filimonov](https://github.com/petethepig), Maintainer +- [Joel Höner](https://github.com/athre0z), Approver +- [Tim Rühsen](https://github.com/rockdaboot), Approver + +For more information about the emeritus role, see the +[community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager). + ## Licenses of dependencies To display a summary of the dependencies' licenses: diff --git a/cli_flags.go b/cli_flags.go index 5a16aa81c..d6ea79d10 100644 --- a/cli_flags.go +++ b/cli_flags.go @@ -28,6 +28,7 @@ const ( defaultArgSendErrorFrames = false defaultOffCPUThreshold = 0 defaultEnvVarsValue = "" + defaultBPFFSRoot = "/sys/fs/bpf/" // This is the X in 2^(n + x) where n is the default hardcoded map size value defaultArgMapScaleFactor = 0 @@ -80,6 +81,9 @@ var ( "Expected format: probe_type:target[:symbol]. probe_type can be kprobe, kretprobe, uprobe, or uretprobe." loadProbeHelper = "Load generic eBPF program that can be attached externally to " + "various user or kernel space hooks." + bpffsHelp = fmt.Sprintf("Set the root BPF FS path for pinned maps. Only used for OBI span/trace ID communication. Default is %s", + defaultBPFFSRoot) + obiProcessCtxHelp = "Load or create a pinned eBPF map for sharing process context information with OBI." ) // Package-scope variable, so that conditionally compiled other components can refer @@ -141,11 +145,15 @@ func parseArgs() (*controller.Config, error) { fs.StringVar(&args.IncludeEnvVars, "env-vars", defaultEnvVarsValue, envVarsHelp) + fs.StringVar(&args.BPFFSRoot, "bpffs-root", defaultBPFFSRoot, bpffsHelp) + fs.Func("probe-link", probeLinkHelper, func(link string) error { args.ProbeLinks = append(args.ProbeLinks, link) return nil }) + fs.BoolVar(&args.OBIProcessCtx, "obi-process-ctx", false, obiProcessCtxHelp) + fs.BoolVar(&args.LoadProbe, "load-probe", false, loadProbeHelper) fs.Usage = func() { @@ -154,6 +162,8 @@ func parseArgs() (*controller.Config, error) { args.Fs = fs + args.ErrorMode = config.PropagateError + return &args, ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("OTEL_PROFILING_AGENT"), ff.WithConfigFileFlag("config"), diff --git a/cmd/otelcol-ebpf-profiler/manifest.yaml b/cmd/otelcol-ebpf-profiler/manifest.yaml index 32ecc7f27..4613073bb 100644 --- a/cmd/otelcol-ebpf-profiler/manifest.yaml +++ b/cmd/otelcol-ebpf-profiler/manifest.yaml @@ -13,19 +13,19 @@ dist: version: dev receivers: - - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0 - - gomod: go.opentelemetry.io/ebpf-profiler v0.0.202610 + - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.152.0 + - gomod: go.opentelemetry.io/ebpf-profiler v0.0.202618 import: go.opentelemetry.io/ebpf-profiler/collector exporters: - - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.148.0 - - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.148.0 - - gomod: go.opentelemetry.io/collector/exporter/otlphttpexporter v0.148.0 + - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.152.0 + - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.152.0 + - gomod: go.opentelemetry.io/collector/exporter/otlphttpexporter v0.152.0 providers: - - gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0 - - gomod: go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0 - - gomod: go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0 + - gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v1.58.0 + - gomod: go.opentelemetry.io/collector/confmap/provider/fileprovider v1.58.0 + - gomod: go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.58.0 replaces: - go.opentelemetry.io/ebpf-profiler => ../../ diff --git a/cmd/otelcol-ebpf-profiler/otel-from-tree.sh b/cmd/otelcol-ebpf-profiler/otel-from-tree.sh index 8be28ae99..3d78ebd88 100755 --- a/cmd/otelcol-ebpf-profiler/otel-from-tree.sh +++ b/cmd/otelcol-ebpf-profiler/otel-from-tree.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +# NOTE: This script needs to be executed from the root directory +# of the repository. + set -euo pipefail # Use COLLECTOR_PATH if set, otherwise default to ../../../opentelemetry-collector @@ -15,12 +18,13 @@ fi echo " # START otel-from-tree - Do not edit below this line" >> cmd/otelcol-ebpf-profiler/manifest.yaml -grep -E "gomod: go.opentelemetry.io/collector/" cmd/otelcol-ebpf-profiler/manifest.yaml | \ - sed -E 's/.*gomod: ([^ ]+) .*/\1/' | \ - sort -u | \ - while read -r module; do - subpath=${module#go.opentelemetry.io/collector} - echo " - ${module} => ${COLLECTOR_PATH}${subpath}" >> cmd/otelcol-ebpf-profiler/manifest.yaml - done +# Replace collector module dependencies with local paths +cd cmd/otelcol-ebpf-profiler && go list -m -u all | \ + grep 'go\.opentelemetry\.io/collector' | \ + awk '{print $1}' | \ + while read -r module; do + subpath=${module#go.opentelemetry.io/collector} + echo " - ${module} => ${COLLECTOR_PATH}${subpath}" >> manifest.yaml + done echo "Local replaces added. You can now build with local opentelemetry-collector changes." diff --git a/collector/.gitignore b/collector/.gitignore new file mode 100644 index 000000000..dec1a32f6 --- /dev/null +++ b/collector/.gitignore @@ -0,0 +1 @@ +generated_*_test.go diff --git a/collector/README.md b/collector/README.md new file mode 100644 index 000000000..4b7ee3f82 --- /dev/null +++ b/collector/README.md @@ -0,0 +1,12 @@ +# eBPF Collector + + +| Status | | +| ------------- |-----------| +| Stability | [development]: profiles | +| Distributions | [ebpf-profiler] | +| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-ebpf-profiler?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fcollector%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-ebpf-profiler/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fcollector) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-ebpf-profiler?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fcollector%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-ebpf-profiler/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fcollector) | + +[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development +[ebpf-profiler]: + diff --git a/collector/config/config.go b/collector/config/config.go index 61aaf3de2..03e2eec54 100644 --- a/collector/config/config.go +++ b/collector/config/config.go @@ -6,7 +6,7 @@ package config // import "go.opentelemetry.io/ebpf-profiler/collector/config" import ( "errors" "fmt" - "runtime" + "strings" "time" "go.opentelemetry.io/ebpf-profiler/internal/linux" @@ -18,6 +18,27 @@ const ( MaxArgMapScaleFactor = 8 ) +// ErrorMode controls how the profiler receiver handles startup errors. +type ErrorMode string + +const ( + // IgnoreError means startup errors are logged but not returned to the collector. + IgnoreError ErrorMode = "ignore" + // PropagateError means startup errors are returned to the collector (default). + PropagateError ErrorMode = "propagate" +) + +func (e *ErrorMode) UnmarshalText(text []byte) error { + str := ErrorMode(strings.ToLower(string(text))) + switch str { + case IgnoreError, PropagateError: + *e = str + return nil + default: + return fmt.Errorf("unknown error mode %q", str) + } +} + // Config is the configuration for the collector. type Config struct { ReporterInterval time.Duration `mapstructure:"reporter_interval"` @@ -40,11 +61,18 @@ type Config struct { NoKernelVersionCheck bool `mapstructure:"no_kernel_version_check"` MaxGRPCRetries uint32 `mapstructure:"max_grpc_retries"` MaxRPCMsgSize int `mapstructure:"max_rpc_msg_size"` + BPFFSRoot string `mapstructure:"bpf_fs_root"` + ErrorMode ErrorMode `mapstructure:"error_mode"` + OBIProcessCtx bool `mapstructure:"obi_process_ctx"` } // Validate validates the config. // This is automatically called by the config parser as it implements the xconfmap.Validator interface. func (cfg *Config) Validate() error { + if cfg.ErrorMode != IgnoreError && cfg.ErrorMode != PropagateError { + return fmt.Errorf("unknown error mode %q", cfg.ErrorMode) + } + if cfg.SamplesPerSecond < 1 { return fmt.Errorf("invalid sampling frequency: %d", cfg.SamplesPerSecond) } @@ -95,17 +123,7 @@ func (cfg *Config) Validate() error { } var minMajor, minMinor uint32 - switch runtime.GOARCH { - case "amd64": - minMajor, minMinor = 5, 2 - case "arm64": - // Older ARM64 kernel versions have broken bpf_probe_read. - // https://github.com/torvalds/linux/commit/6ae08ae3dea2cfa03dd3665a3c8475c2d429ef47 - minMajor, minMinor = 5, 5 - default: - return fmt.Errorf("unsupported architecture: %s", runtime.GOARCH) - } - + minMajor, minMinor = 5, 10 if major < minMajor || (major == minMajor && minor < minMinor) { return fmt.Errorf("host Agent requires kernel version "+ "%d.%d or newer but got %d.%d.%d", minMajor, minMinor, major, minor, patch) diff --git a/collector/config/config_test.go b/collector/config/config_test.go index 63b27b237..441fcdefa 100644 --- a/collector/config/config_test.go +++ b/collector/config/config_test.go @@ -5,16 +5,111 @@ package config // import "go.opentelemetry.io/ebpf-profiler/collector/config" import ( "testing" + "time" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/confmap/xconfmap" ) +// validConfig returns a config with valid defaults for testing. +func validConfig() *Config { + return &Config{ + SamplesPerSecond: 20, + ProbabilisticInterval: 1 * time.Minute, + ProbabilisticThreshold: 100, + NoKernelVersionCheck: true, + } +} + func TestValidate(t *testing.T) { cfg := &Config{ SamplesPerSecond: 0, + ErrorMode: PropagateError, } err := xconfmap.Validate(cfg) require.Error(t, err) require.Equal(t, "invalid sampling frequency: 0", err.Error()) } + +func TestUnmarshalText(t *testing.T) { + for _, tt := range []struct { + name string + input string + want ErrorMode + wantErr bool + }{ + { + name: "ignore", + input: "ignore", + want: IgnoreError, + }, + { + name: "propagate", + input: "propagate", + want: PropagateError, + }, + { + name: "case insensitive", + input: "IGNORE", + want: IgnoreError, + }, + { + name: "invalid value", + input: "INVALID", + wantErr: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + var e ErrorMode + err := e.UnmarshalText([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, e) + }) + } +} + +func TestValidateErrorMode(t *testing.T) { + for _, tt := range []struct { + name string + errorMode ErrorMode + want ErrorMode + wantErr bool + }{ + { + name: "empty error mode is invalid", + errorMode: "", + wantErr: true, + }, + { + name: "ignore is valid", + errorMode: IgnoreError, + want: IgnoreError, + }, + { + name: "propagate is valid", + errorMode: PropagateError, + want: PropagateError, + }, + { + name: "invalid error mode", + errorMode: "INVALID", + wantErr: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + cfg := validConfig() + cfg.ErrorMode = tt.errorMode + err := xconfmap.Validate(cfg) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, cfg.ErrorMode) + }) + } +} diff --git a/collector/factory.go b/collector/factory.go index 86c697585..0fb8804c3 100644 --- a/collector/factory.go +++ b/collector/factory.go @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +//go:generate go tool -modfile=../internal/tools/go.mod mdatagen metadata.yaml + package collector // import "go.opentelemetry.io/ebpf-profiler/collector" import ( @@ -12,20 +14,17 @@ import ( "go.opentelemetry.io/collector/receiver/xreceiver" "go.opentelemetry.io/ebpf-profiler/collector/config" + "go.opentelemetry.io/ebpf-profiler/collector/internal/metadata" ) -var ( - typeStr = component.MustNewType("profiling") - - errInvalidConfig = errors.New("invalid config") -) +var errInvalidConfig = errors.New("invalid config") // NewFactory creates a factory for the receiver. func NewFactory() receiver.Factory { return xreceiver.NewFactory( - typeStr, + metadata.Type, defaultConfig, - xreceiver.WithProfiles(BuildProfilesReceiver(), component.StabilityLevelAlpha)) + xreceiver.WithProfiles(BuildProfilesReceiver(), metadata.ProfilesStability)) } func defaultConfig() component.Config { @@ -40,5 +39,7 @@ func defaultConfig() component.Config { ClockSyncInterval: 3 * time.Minute, MaxGRPCRetries: 5, MaxRPCMsgSize: 32 << 20, // 32 MiB, + BPFFSRoot: "/sys/fs/bpf/", + ErrorMode: config.PropagateError, } } diff --git a/collector/internal/controller.go b/collector/internal/controller.go index 2cf25bbd9..b76eabee6 100644 --- a/collector/internal/controller.go +++ b/collector/internal/controller.go @@ -9,8 +9,9 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer/xconsumer" "go.opentelemetry.io/collector/receiver" - + "go.opentelemetry.io/ebpf-profiler/collector/config" "go.opentelemetry.io/ebpf-profiler/internal/controller" + "go.opentelemetry.io/ebpf-profiler/internal/log" "go.opentelemetry.io/ebpf-profiler/metrics" "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/times" @@ -22,10 +23,11 @@ const ( ) // Controller is a bridge between the Collector's [receiverprofiles.Profiles] -// interface and our [internal.Controller] +// interface and our [internal.Controller]. type Controller struct { ctlr *controller.Controller onShutdown func() error + errorMode config.ErrorMode } func NewController(cfg *controller.Config, rs receiver.Settings, @@ -64,12 +66,21 @@ func NewController(cfg *controller.Config, rs receiver.Settings, return &Controller{ onShutdown: cfg.OnShutdown, ctlr: controller.New(cfg), + errorMode: cfg.ErrorMode, }, nil } // Start starts the receiver. func (c *Controller) Start(ctx context.Context, _ component.Host) error { - return c.ctlr.Start(ctx) + if err := c.ctlr.Start(ctx); err != nil { + if c.errorMode == config.IgnoreError { + c.ctlr.Shutdown() + log.Errorf("eBPF profiler receiver failed, continuing without profiling: %v", err) + return nil + } + return err + } + return nil } // Shutdown stops the receiver. diff --git a/collector/internal/metadata/generated_status.go b/collector/internal/metadata/generated_status.go new file mode 100644 index 000000000..3b975f514 --- /dev/null +++ b/collector/internal/metadata/generated_status.go @@ -0,0 +1,18 @@ +// Code generated by mdatagen. DO NOT EDIT. + +// Package metadata contains the autogenerated telemetry and +// build information for the receiver/profiling component. +package metadata + +import ( + "go.opentelemetry.io/collector/component" +) + +var ( + Type = component.MustNewType("profiling") + ScopeName = "go.opentelemetry.io/ebpf-profiler/collector" +) + +const ( + ProfilesStability = component.StabilityLevelDevelopment +) diff --git a/collector/metadata.yaml b/collector/metadata.yaml new file mode 100644 index 000000000..bc68abc7f --- /dev/null +++ b/collector/metadata.yaml @@ -0,0 +1,11 @@ +type: profiling +github_project: open-telemetry/opentelemetry-ebpf-profiler + +status: + disable_codecov_badge: true + class: receiver + stability: + development: [profiles] + distributions: [ebpf-profiler] +tests: + skip_lifecycle: true diff --git a/collector/start_test.go b/collector/start_test.go new file mode 100644 index 000000000..cd0128905 --- /dev/null +++ b/collector/start_test.go @@ -0,0 +1,82 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//go:build linux && (amd64 || arm64) + +package collector + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/consumer/xconsumer" + "go.opentelemetry.io/collector/receiver/receivertest" + + "go.opentelemetry.io/ebpf-profiler/collector/config" + "go.opentelemetry.io/ebpf-profiler/libpf" + "go.opentelemetry.io/ebpf-profiler/reporter" + "go.opentelemetry.io/ebpf-profiler/reporter/samples" +) + +// dummyReporter is a no-op reporter for testing. +type dummyReporter struct{} + +func (d *dummyReporter) Start(context.Context) error { return fmt.Errorf("dummy error") } +func (d *dummyReporter) Stop() {} +func (d *dummyReporter) ReportTraceEvent(*libpf.Trace, *samples.TraceEventMeta) error { return nil } + +// TestStartErrorMode tests the error_mode config option on controller Start(). +// dummyReporter.Start() always returns an error to simulate startup failure. +func TestStartErrorMode(t *testing.T) { + dummyFactory := func(_ *reporter.Config, _ xconsumer.Profiles) (reporter.Reporter, error) { + return &dummyReporter{}, nil + } + + for _, tt := range []struct { + name string + errorMode config.ErrorMode + wantErr bool + }{ + { + name: "propagate returns error", + errorMode: config.PropagateError, + wantErr: true, + }, + { + name: "ignore returns nil", + errorMode: config.IgnoreError, + wantErr: false, + }, + } { + t.Run(tt.name, func(t *testing.T) { + cfg := defaultConfig().(*config.Config) + cfg.ErrorMode = tt.errorMode + cfg.NoKernelVersionCheck = true + + typ, err := component.NewType("test") + require.NoError(t, err) + + recv, err := BuildProfilesReceiver( + WithReporterFactory(dummyFactory), + )( + t.Context(), + receivertest.NewNopSettings(typ), + cfg, + consumertest.NewNop(), + ) + require.NoError(t, err) + + err = recv.Start(t.Context(), componenttest.NewNopHost()) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/doc/KNOWN_KERNEL_LIMITATIONS.md b/doc/KNOWN_KERNEL_LIMITATIONS.md deleted file mode 100644 index 030f0fd81..000000000 --- a/doc/KNOWN_KERNEL_LIMITATIONS.md +++ /dev/null @@ -1,33 +0,0 @@ -Known limitations -================= -The Linux kernel is constantly evolving and so is eBPF. To be able to load our eBPF code with older kernel versions we have to write code to avoid some limitations. This file documents the restrictions we ran into while writing the code. - -Number of tracepoints ---------------------- -Affects kernel < 4.15. - -There was a limit of 1 eBPF program per tracepoint/kprobe. -This limit no longer holds and was removed with commit [e87c6bc3852b981e71c757be20771546ce9f76f3](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e87c6bc3852b981e71c757be20771546ce9f76f3). - - -Kernel version check --------------------- -Affects kernel < 5.0. - -As part of the verification of eBPF programs, the `kern_version` attribute was checked and it needed to match with the currently running kernel version. -This check was removed with commit [6c4fc209fcf9d27efbaa48368773e4d2bfbd59aa](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6c4fc209fcf9d27efbaa48368773e4d2bfbd59aa). - - -eBPF instruction limit ----------------------- -Affects kernel < 5.2. - -The number of eBPF instructions per program was limited to 4096 instructions. -This limit was raised to 1 million eBPF instructions with commit [c04c0d2b968ac45d6ef020316808ef6c82325a82](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c04c0d2b968ac45d6ef020316808ef6c82325a82). - - -eBPF inner arrays (map-in-map) must be of same size ---------------------------------------------------- -Affects kernel < 5.10. - -This restriction was removed with commit[4a8f87e60f6db40e640f1db555d063b2c4dea5f1](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4a8f87e60f6db40e640f1db555d063b2c4dea5f1). diff --git a/doc/internals.md b/doc/internals.md index a0a52fc2e..299698a97 100644 --- a/doc/internals.md +++ b/doc/internals.md @@ -206,16 +206,10 @@ of these limitations are significantly relaxed in newer kernel versions, but we still have to stick to the old limits because we wish to continue supporting older kernels. -The minimum supported Linux kernel versions are -- 5.4 for amd64/x86_64 -- 5.5 for arm64/aarch64 +The minimum supported Linux kernel versions is 5.10 for amd64/x86_64 and arm64/aarch64. The most notable limitations are the following two: -- **4096 instructions per program**\ - A single BPF program can consist of a maximum of 4096 instructions, otherwise - older kernels will refuse to load it. Since BPF does not allow for loops, they - instead need to be unrolled. - **32 tail-calls**\ Linux allows BPF programs to do a tail-call to another BPF program. A tail call is essentially a `jmp` into another BPF program, ending execution of the diff --git a/go.mod b/go.mod index e6e1cce07..fbbb1a382 100644 --- a/go.mod +++ b/go.mod @@ -11,59 +11,62 @@ module go.opentelemetry.io/ebpf-profiler go 1.25.0 require ( - github.com/aws/aws-sdk-go-v2 v1.41.4 - github.com/aws/aws-sdk-go-v2/config v1.32.12 - github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2 + github.com/aws/aws-sdk-go-v2 v1.41.7 + github.com/aws/aws-sdk-go-v2/config v1.32.17 + github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 github.com/cilium/ebpf v0.21.0 github.com/elastic/go-freelru v0.16.0 github.com/elastic/go-perf v0.0.0-20260224073651-af0ee0c731b7 github.com/google/uuid v1.6.0 - github.com/klauspost/compress v1.18.5 + github.com/klauspost/compress v1.18.6 github.com/mdlayher/kobject v0.0.0-20200520190114-19ca17470d7d github.com/minio/sha256-simd v1.0.1 - github.com/open-telemetry/sig-profiling/tools/profcheck v0.0.0-20260303084341-52f633d434c9 + github.com/open-telemetry/sig-profiling/profcheck v0.0.0-20260513195436-166e8d1c6b3d github.com/peterbourgon/ff/v3 v3.4.0 github.com/stretchr/testify v1.11.1 github.com/zeebo/xxh3 v1.1.0 - go.opentelemetry.io/collector/component v1.54.0 - go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 - go.opentelemetry.io/collector/consumer/consumertest v0.148.0 - go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 - go.opentelemetry.io/collector/pdata v1.54.0 - go.opentelemetry.io/collector/pdata/pprofile v0.148.0 - go.opentelemetry.io/collector/receiver v1.54.0 - go.opentelemetry.io/collector/receiver/receivertest v0.148.0 - go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 - go.opentelemetry.io/otel v1.42.0 - go.opentelemetry.io/otel/metric v1.42.0 + go.opentelemetry.io/collector/component v1.58.0 + go.opentelemetry.io/collector/component/componenttest v0.152.0 + go.opentelemetry.io/collector/confmap v1.58.0 + go.opentelemetry.io/collector/confmap/xconfmap v0.152.0 + go.opentelemetry.io/collector/consumer/consumertest v0.152.0 + go.opentelemetry.io/collector/consumer/xconsumer v0.152.0 + go.opentelemetry.io/collector/pdata v1.58.0 + go.opentelemetry.io/collector/pdata/pprofile v0.152.0 + go.opentelemetry.io/collector/receiver v1.58.0 + go.opentelemetry.io/collector/receiver/receivertest v0.152.0 + go.opentelemetry.io/collector/receiver/xreceiver v0.152.0 + go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel/metric v1.43.0 + go.opentelemetry.io/proto/otlp v1.10.0 go.opentelemetry.io/proto/otlp/profiles/v1development v0.3.0 + go.uber.org/goleak v1.3.0 go.uber.org/zap/exp v0.3.0 - golang.org/x/arch v0.25.0 - golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 - golang.org/x/mod v0.34.0 + golang.org/x/arch v0.27.0 + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a + golang.org/x/mod v0.36.0 golang.org/x/sync v0.20.0 - golang.org/x/sys v0.42.0 - google.golang.org/grpc v1.79.3 + golang.org/x/sys v0.44.0 + google.golang.org/grpc v1.81.0 google.golang.org/protobuf v1.36.11 ) require ( - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect - github.com/aws/smithy-go v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect + github.com/aws/smithy-go v1.25.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -71,14 +74,14 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/hashicorp/go-version v1.8.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/jsimonetti/rtnetlink/v2 v2.0.3 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/providers/confmap v1.0.0 // indirect - github.com/knadh/koanf/v2 v2.3.3 // indirect + github.com/knadh/koanf/v2 v2.3.4 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -87,22 +90,19 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/collector/component/componenttest v0.148.0 // indirect - go.opentelemetry.io/collector/confmap v1.54.0 // indirect - go.opentelemetry.io/collector/consumer v1.54.0 // indirect - go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect - go.opentelemetry.io/collector/featuregate v1.54.0 // indirect - go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect - go.opentelemetry.io/collector/pipeline v1.54.0 // indirect - go.opentelemetry.io/otel/sdk v1.42.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect - go.opentelemetry.io/otel/trace v1.42.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/collector/consumer v1.58.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror v0.152.0 // indirect + go.opentelemetry.io/collector/featuregate v1.58.0 // indirect + go.opentelemetry.io/collector/internal/componentalias v0.152.0 // indirect + go.opentelemetry.io/collector/pipeline v1.58.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.1 // indirect + go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/text v0.34.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 63163e329..6401674bb 100644 --- a/go.sum +++ b/go.sum @@ -1,41 +1,39 @@ -github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= -github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= -github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= -github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= -github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= -github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 h1:SwGMTMLIlvDNyhMteQ6r8IJSBPlRdXX5d4idhIGbkXA= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21/go.mod h1:UUxgWxofmOdAMuqEsSppbDtGKLfR04HGsD0HXzvhI1k= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 h1:qtJZ70afD3ISKWnoX3xB0J2otEqu3LqicRcDBqsj0hQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12/go.mod h1:v2pNpJbRNl4vEUWEh5ytQok0zACAKfdmKS51Hotc3pQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 h1:siU1A6xjUZ2N8zjTHSXFhB9L/2OY8Dqs0xXiLjF30jA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20/go.mod h1:4TLZCmVJDM3FOu5P5TJP0zOlu9zWgDWU7aUxWbr+rcw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2 h1:MRNiP6nqa20aEl8fQ6PJpEq11b2d40b16sm4WD7QgMU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2/go.mod h1:FrNA56srbsr3WShiaelyWYEo70x80mXnVZ17ZZfbeqg= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= -github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= -github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY= +github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU= +github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio= +github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= +github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.21.0 h1:4dpx1J/B/1apeTmWBH5BkVLayHTkFrMovVPnHEk+l3k= @@ -70,8 +68,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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 v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= @@ -80,16 +78,16 @@ github.com/jsimonetti/rtnetlink/v2 v2.0.3 h1:Jcp7GTnTPepoUAJ9+LhTa7ZiebvNS56T1Gt 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/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= -github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE= github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A= -github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94= -github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= +github.com/knadh/koanf/v2 v2.3.4 h1:fnynNSDlujWE+v83hAp8wKr/cdoxHLO0629SN+U8Urc= +github.com/knadh/koanf/v2 v2.3.4/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -115,8 +113,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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/open-telemetry/sig-profiling/tools/profcheck v0.0.0-20260303084341-52f633d434c9 h1:3NStK3r8FVhXbU0qkVz/DpPQlaoLLgLHJOAMKyDX4WM= -github.com/open-telemetry/sig-profiling/tools/profcheck v0.0.0-20260303084341-52f633d434c9/go.mod h1:KRO+Rec0+KycN1CrIP/6Pu0xOraPhbahCbL36i8FkfM= +github.com/open-telemetry/sig-profiling/profcheck v0.0.0-20260513195436-166e8d1c6b3d h1:qPt934sYH3CmG05SSHFXWiWTP87W60BpIvmuuiy6jTw= +github.com/open-telemetry/sig-profiling/profcheck v0.0.0-20260513195436-166e8d1c6b3d/go.mod h1:WPdgk1BinVdvYdkbt1KIkgDw6qUiK8CnGq+ftaJ41Ns= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -134,52 +132,52 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/collector/component v1.54.0 h1:LvtX0Tzz18n44OrUFVk77N1FNsejfWJqztB28hrmDM8= -go.opentelemetry.io/collector/component v1.54.0/go.mod h1:yUMBYsySY/sDcXm8kOzEoZxt+JLdala6hxzSW0npOxY= -go.opentelemetry.io/collector/component/componenttest v0.148.0 h1:tBXJWmy2X6KD8S0QU2YZa2zYBqP+IycSM4iOtwDD2pA= -go.opentelemetry.io/collector/component/componenttest v0.148.0/go.mod h1:1c1+6mZOmI0raoya5vA/X0F+fawEjNS6tCEs5xLATtA= -go.opentelemetry.io/collector/confmap v1.54.0 h1:RUoxQ4uAYHTI57GfHh61D00tTQsXm9T88ozrAiicByc= -go.opentelemetry.io/collector/confmap v1.54.0/go.mod h1:mQxG8bk0IWIt9gbWMvzE+cRkOuCuzbzkNGBq2YJ4wNM= -go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 h1:UW8MX5VlKJf67x4Et7J9kPwP9Rv4VSmJ+UUpgRcb//c= -go.opentelemetry.io/collector/confmap/xconfmap v0.148.0/go.mod h1:4qTMr3V0uSXXac9wVs/UD5fIqRKw5yIl58+Vjsc6RHM= -go.opentelemetry.io/collector/consumer v1.54.0 h1:RGGtUN+GbkV1px3T6XdUHmgJ+ldJ1hAHdesFzW/wgL0= -go.opentelemetry.io/collector/consumer v1.54.0/go.mod h1:1PC6XINTL9DdT1bwvfMdHE72EB4RWU/WcPemUrhqKN8= -go.opentelemetry.io/collector/consumer/consumererror v0.148.0 h1:lKVkNWBeRXG41lHBf5KzA9oErRZifx6qTd9erAFfEkE= -go.opentelemetry.io/collector/consumer/consumererror v0.148.0/go.mod h1:N/UppmtknIdzpEiy3xirH1EiBEBOqKqD77NCyNi2Rbc= -go.opentelemetry.io/collector/consumer/consumertest v0.148.0 h1:ms0HtWMj17tI1Yds0hSuUI5QYpNEqd11AAhwIoUY2HE= -go.opentelemetry.io/collector/consumer/consumertest v0.148.0/go.mod h1:wScw/OzKkf/ZzJn4ToI30OoI1kJiY16WNrcFToXSzK0= -go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 h1:m3b9rY7CLD5Pcge6sSKHIT3OlcPN6xqYsdtVs9oJ528= -go.opentelemetry.io/collector/consumer/xconsumer v0.148.0/go.mod h1:bG+Wz6xmIBl/gHzq1sqvksWXqTLuTX17Wo//zIsdZpw= -go.opentelemetry.io/collector/featuregate v1.54.0 h1:ufo5Hy4Co9pcHVg24hyanm8qFG3TkkYbVyQXPVAbwDc= -go.opentelemetry.io/collector/featuregate v1.54.0/go.mod h1:PS7zY/zaCb28EqciePVwRHVhc3oKortTFXsi3I6ee4g= -go.opentelemetry.io/collector/internal/componentalias v0.148.0 h1:Y6MftNIZSzOr47TTj6A2z2UR3IwbeG46sAQshicGtDg= -go.opentelemetry.io/collector/internal/componentalias v0.148.0/go.mod h1:uwKzfehzwRgHxdHgFXYSBHNBeWSSqsqQYGWr5fk08G0= -go.opentelemetry.io/collector/internal/testutil v0.148.0 h1:3Z9hperte3vSmbBTYeNndoEUICICrNz8hzx+v0FYXBQ= -go.opentelemetry.io/collector/internal/testutil v0.148.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= -go.opentelemetry.io/collector/pdata v1.54.0 h1:3LharKb792cQ3VrUGxd3IcpWwfu3ST+GSTU382jVz1s= -go.opentelemetry.io/collector/pdata v1.54.0/go.mod h1:+MqC3VVOv/EX9YVFUo+mI4F0YmwJ+fXBYwjmu+mRiZ8= -go.opentelemetry.io/collector/pdata/pprofile v0.148.0 h1:MgrNZmqwhZGfiYwcKKtM/iXgTZqqvG5dUphriRXMZHU= -go.opentelemetry.io/collector/pdata/pprofile v0.148.0/go.mod h1:MTTMnZPqWX1S/rBDatU0W19udlycBkWuzVV5qnemHdc= -go.opentelemetry.io/collector/pdata/testdata v0.148.0 h1:yzakPuFgoKK8WcrlhyYHLMLA/kLScQKGsXkIgwieAQ8= -go.opentelemetry.io/collector/pdata/testdata v0.148.0/go.mod h1:2rFvxm8qwd3nlO90FtJw6ZGAjt+bLndxmQuJaMO9kfQ= -go.opentelemetry.io/collector/pipeline v1.54.0 h1:jYlCkdFLITVBdeB+IGS07zXWywEgvT3Ky46vdKKT+Ks= -go.opentelemetry.io/collector/pipeline v1.54.0/go.mod h1:RD90NG3Jbk965Xaqym3JyHkuol4uZJjQVUkD9ddXJIs= -go.opentelemetry.io/collector/receiver v1.54.0 h1:2e9o+eihZ/nJnzVj5JAcJ+VQ653HcZRiT127qBZRqa8= -go.opentelemetry.io/collector/receiver v1.54.0/go.mod h1:xFZnvYTBjdi9iS/d/UUXzss4h311mLsZliQFQXk4o/k= -go.opentelemetry.io/collector/receiver/receivertest v0.148.0 h1:Fu+B4jCqgZVZmhsKBz3tcgimFryR6TRAK2D5VGLD2Xc= -go.opentelemetry.io/collector/receiver/receivertest v0.148.0/go.mod h1:K8dMDMEggEg6jB688VOHutivOGEEZ20FJGe4jV9RtWU= -go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 h1:u66Zi3udD9RMRiNOsZzsVcUjRwqJEK+5LV76Ry9l3K0= -go.opentelemetry.io/collector/receiver/xreceiver v0.148.0/go.mod h1:jyHxf8SOfH48ZXb32IS3vPbVYDinsLlZYQddyrveqMg= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= -go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= -go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= -go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/collector/component v1.58.0 h1:GLHxFfw8jw3lRVX/h09Cc3inoR+2+XY93VxnrwHxY8Q= +go.opentelemetry.io/collector/component v1.58.0/go.mod h1:oAA7bLZUz/j1r16bQ9Cu/F+72xbQxlci8H2VmqtsAGE= +go.opentelemetry.io/collector/component/componenttest v0.152.0 h1:4o44irB1ERGRk+J5WSBvvhM9DFmIiDfC19RhPacRhOE= +go.opentelemetry.io/collector/component/componenttest v0.152.0/go.mod h1:+K7/zIUH9L6iE8mDOumJCBenPV6XxdyGIQPuvR6hIx4= +go.opentelemetry.io/collector/confmap v1.58.0 h1:lKk7XZ/BEA0eSlQWanBkhjDZewB/tu5EK2+PV/qlBws= +go.opentelemetry.io/collector/confmap v1.58.0/go.mod h1:2O/WadVBFwRzpO+3skcvjqDxD+OaS0TKKDDpPBaR4bs= +go.opentelemetry.io/collector/confmap/xconfmap v0.152.0 h1:U3h6Nf5dIaVTJTQYXGJdyIYOhaYJVEEyabXc1isU0Js= +go.opentelemetry.io/collector/confmap/xconfmap v0.152.0/go.mod h1:ff7vNJZ/kkN9pMEXRM0T9TeaKcCZE226I2NlJhKXF3I= +go.opentelemetry.io/collector/consumer v1.58.0 h1:R9qWrp4xlZTrT+Ph10vgrjnaIzkW20/RAbXs2F7PhPA= +go.opentelemetry.io/collector/consumer v1.58.0/go.mod h1:ptX5120b3Py+vqBUmjvl5VJ8yYpSQOto6Vv9b12f2d4= +go.opentelemetry.io/collector/consumer/consumererror v0.152.0 h1:ymlWj9j9KbU+x1fbnpEOd0/XRek5zsLKpNkLdmrVkYo= +go.opentelemetry.io/collector/consumer/consumererror v0.152.0/go.mod h1:snNSTrDfd8fgi1M1etHTzO3TnuqIXfuJcnRT8NUH08s= +go.opentelemetry.io/collector/consumer/consumertest v0.152.0 h1:PDYdCdbZCDWRM/XsqFTsc6BKzXwKa6+tjGe209Gv4j0= +go.opentelemetry.io/collector/consumer/consumertest v0.152.0/go.mod h1:duKfkI7aFLybPa0mFMcNtpvniZIOqIzl1CjToEJpzJU= +go.opentelemetry.io/collector/consumer/xconsumer v0.152.0 h1:+tcm9JCiQki+EpdFGxN4G8Mt0aiSLkQHYqNEXsinHd8= +go.opentelemetry.io/collector/consumer/xconsumer v0.152.0/go.mod h1:Efuqcxa8IEkXwHdwPAUYQprGWUpIO43QjoDSWKQFSiQ= +go.opentelemetry.io/collector/featuregate v1.58.0 h1:Kh6Dpgbxywv/Q3D6qPehaSxNCxvr/U/ki7CL4y3udCo= +go.opentelemetry.io/collector/featuregate v1.58.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= +go.opentelemetry.io/collector/internal/componentalias v0.152.0 h1:5uwYJ+F37s882FLzcE8ZBvCyLtcGGQsRQrNkXxYMApk= +go.opentelemetry.io/collector/internal/componentalias v0.152.0/go.mod h1:RPRS7z22S6yb+hTYzr67HoYyyB+qCKPXMaYafvdPb5s= +go.opentelemetry.io/collector/internal/testutil v0.152.0 h1:8LGwekR7mLcUDhT1ofLmdnrHRFuUa3U7PBd95ZvJEjQ= +go.opentelemetry.io/collector/internal/testutil v0.152.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= +go.opentelemetry.io/collector/pdata v1.58.0 h1:5Lxut3NxKp87066Pzt+3q7+JUuFI5B3teCyLZIF8wIs= +go.opentelemetry.io/collector/pdata v1.58.0/go.mod h1:4vZtODINbC/JF3eGocnatdImzbRHseOywIcr+aULjCg= +go.opentelemetry.io/collector/pdata/pprofile v0.152.0 h1:hXpfrauR0vw2VeiYj3AGv5IySbWz56zltUtzEsLf82s= +go.opentelemetry.io/collector/pdata/pprofile v0.152.0/go.mod h1:+5gGwrj8zQuP7AGy1c8pfm8hSYTjPTdWqllZy/5rDyM= +go.opentelemetry.io/collector/pdata/testdata v0.152.0 h1:FCPsXKQJQ5ftlfJze9vtsBcFQ5sEk9tmxeBMdf6dcE0= +go.opentelemetry.io/collector/pdata/testdata v0.152.0/go.mod h1:Csws6zIBIRox4w0F4vMCPCytdN+4qg0Bdp10axC/8uw= +go.opentelemetry.io/collector/pipeline v1.58.0 h1:jgSupJSxLDdpt4RqmG9eaibOzGIu3clSVe6q4H7tRJs= +go.opentelemetry.io/collector/pipeline v1.58.0/go.mod h1:RD90NG3Jbk965Xaqym3JyHkuol4uZJjQVUkD9ddXJIs= +go.opentelemetry.io/collector/receiver v1.58.0 h1:0GT+JVJOegia6+A14EOyCJQhXK3+/NoS8bg7gqjOadM= +go.opentelemetry.io/collector/receiver v1.58.0/go.mod h1:svgNcdk9hxFTvAPJYpydDUHx6AvCBYLjEhx0o+TabNA= +go.opentelemetry.io/collector/receiver/receivertest v0.152.0 h1:aso81TPkHZtxZffCD+kY4RWCX3uHH8knJLq+6rWSEao= +go.opentelemetry.io/collector/receiver/receivertest v0.152.0/go.mod h1:rBZkVFtjUy5pybspLnok28rPxtB5ljQS5TdcRrcd5PE= +go.opentelemetry.io/collector/receiver/xreceiver v0.152.0 h1:Brz/xsi9NV2r3etNGxfe45b2c6YrQ7JTLCHvCwaquTo= +go.opentelemetry.io/collector/receiver/xreceiver v0.152.0/go.mod h1:V6VMdl4T5QFr4hLn79iVHOC2v3dhQRJXGsVoxkB18tA= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.opentelemetry.io/proto/otlp/profiles/v1development v0.3.0 h1:ZQs05qo3Yh4KUHeVH6v89xErwmsvgA/cLX2/w5Ikp+k= @@ -194,25 +192,25 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE= -golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/arch v0.27.0 h1:0WNVcR8u9yFz8j5FvdHpgwNp3FS5U4guYdzHwEiGjoU= +golang.org/x/arch v0.27.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= -golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -221,18 +219,18 @@ golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= +google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/controller/controller.go b/internal/controller/controller.go index cb3e378b0..9bdd602a8 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -109,6 +109,8 @@ func (c *Controller) Start(ctx context.Context) error { ProbeLinks: c.config.ProbeLinks, LoadProbe: c.config.LoadProbe, ExecutableReporter: c.config.ExecutableReporter, + BPFFSRoot: c.config.BPFFSRoot, + OBIProcessCtx: c.config.OBIProcessCtx, }) if err != nil { return fmt.Errorf("failed to load eBPF tracer: %w", err) diff --git a/tools.mod b/internal/tools/go.mod similarity index 64% rename from tools.mod rename to internal/tools/go.mod index f5354610f..c4179dca7 100644 --- a/tools.mod +++ b/internal/tools/go.mod @@ -1,4 +1,4 @@ -module go.opentelemetry.io/ebpf-profiler +module github.com/open-telemetry/opentelemetry-ebpf-profiler/internal/tools go 1.25.0 @@ -7,49 +7,14 @@ tool ( github.com/google/go-licenses/v2 github.com/jcchavezs/porto/cmd/porto go.opentelemetry.io/collector/cmd/builder + go.opentelemetry.io/collector/cmd/mdatagen gotest.tools/gotestsum ) -require ( - github.com/aws/aws-sdk-go-v2 v1.41.4 - github.com/aws/aws-sdk-go-v2/config v1.32.12 - github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2 - github.com/cilium/ebpf v0.21.0 - github.com/elastic/go-freelru v0.16.0 - github.com/elastic/go-perf v0.0.0-20260224073651-af0ee0c731b7 - github.com/google/uuid v1.6.0 - github.com/klauspost/compress v1.18.5 - github.com/mdlayher/kobject v0.0.0-20200520190114-19ca17470d7d - github.com/minio/sha256-simd v1.0.1 - github.com/open-telemetry/sig-profiling/tools/profcheck v0.0.0-20260303084341-52f633d434c9 - github.com/peterbourgon/ff/v3 v3.4.0 - github.com/stretchr/testify v1.11.1 - github.com/zeebo/xxh3 v1.1.0 - go.opentelemetry.io/collector/component v1.54.0 - go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 - go.opentelemetry.io/collector/consumer/consumertest v0.148.0 - go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 - go.opentelemetry.io/collector/pdata v1.54.0 - go.opentelemetry.io/collector/pdata/pprofile v0.148.0 - go.opentelemetry.io/collector/receiver v1.54.0 - go.opentelemetry.io/collector/receiver/receivertest v0.148.0 - go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 - go.opentelemetry.io/otel v1.42.0 - go.opentelemetry.io/otel/metric v1.42.0 - go.opentelemetry.io/proto/otlp/profiles/v1development v0.3.0 - go.uber.org/zap/exp v0.3.0 - golang.org/x/arch v0.25.0 - golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 - golang.org/x/mod v0.34.0 - golang.org/x/sync v0.20.0 - golang.org/x/sys v0.42.0 - google.golang.org/grpc v1.79.3 - google.golang.org/protobuf v1.36.11 -) - require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect + charm.land/lipgloss/v2 v2.0.3 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect @@ -62,73 +27,60 @@ require ( github.com/Antonboom/nilnil v1.1.1 // indirect github.com/Antonboom/testifylint v1.6.4 // indirect github.com/BurntSushi/toml v1.6.0 // indirect + github.com/ClickHouse/clickhouse-go-linter v1.2.0 // indirect github.com/Djarvur/go-err113 v0.1.1 // indirect - github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/MirrexOne/unqueryvet v1.5.4 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect - github.com/alecthomas/chroma/v2 v2.23.1 // indirect + github.com/alecthomas/chroma/v2 v2.24.1 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.6 // indirect github.com/alexkohler/prealloc v1.1.0 // indirect github.com/alfatraining/structtag v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/alingse/nilnesserr v0.2.0 // indirect - github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect - github.com/ashanbrown/makezero/v2 v2.1.0 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect - github.com/aws/smithy-go v1.24.2 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/ashanbrown/forbidigo/v2 v2.3.1 // indirect + github.com/ashanbrown/makezero/v2 v2.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bitfield/gotestdox v0.2.2 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v4 v4.7.0 // indirect - github.com/bombsimon/wsl/v5 v5.6.0 // indirect + github.com/bombsimon/wsl/v5 v5.8.0 // indirect github.com/breml/bidichk v0.3.3 // indirect github.com/breml/errchkjson v0.4.1 // indirect - github.com/butuzov/ireturn v0.4.0 // indirect + github.com/butuzov/ireturn v0.4.1 // indirect github.com/butuzov/mirror v1.3.0 // indirect github.com/catenacyber/perfsprint v0.10.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.4 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.11 // indirect - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/lipgloss v1.1.0 // indirect - github.com/charmbracelet/x/ansi v0.10.1 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect + github.com/charmbracelet/x/ansi v0.11.7 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.2 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.7 // indirect github.com/dave/dst v0.27.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/dlclark/regexp2 v1.12.0 // indirect github.com/dnephin/pflag v1.0.7 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.19.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.6 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.20 // indirect github.com/go-critic/go-critic v0.14.3 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -144,14 +96,15 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golangci/asciicheck v0.5.0 // indirect - github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect + github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect - github.com/golangci/golangci-lint/v2 v2.11.4 // indirect + github.com/golangci/golangci-lint/v2 v2.12.2 // indirect github.com/golangci/golines v0.15.0 // indirect github.com/golangci/misspell v0.8.0 // indirect github.com/golangci/plugin-module-register v0.1.2 // indirect github.com/golangci/revgrep v0.8.0 // indirect + github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba // indirect github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect github.com/google/go-cmp v0.7.0 // indirect @@ -164,29 +117,26 @@ require ( github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.2 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect - github.com/hashicorp/go-version v1.8.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jcchavezs/porto v0.7.0 // indirect - github.com/jgautheron/goconst v1.8.2 // indirect - github.com/jingyugao/rowserrcheck v1.1.1 // indirect + github.com/jgautheron/goconst v1.10.0 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect - github.com/josharian/native v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect github.com/kisielk/errcheck v1.10.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/parsers/yaml v1.1.0 // indirect github.com/knadh/koanf/providers/confmap v1.0.0 // indirect github.com/knadh/koanf/providers/env/v2 v2.0.0 // indirect github.com/knadh/koanf/providers/file v1.2.1 // indirect github.com/knadh/koanf/providers/fs v1.0.0 // indirect - github.com/knadh/koanf/v2 v2.3.3 // indirect + github.com/knadh/koanf/v2 v2.3.4 // indirect github.com/kulti/thelper v0.7.1 // indirect github.com/kunwardeep/paralleltest v1.0.15 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect @@ -197,20 +147,18 @@ require ( github.com/ldez/tagliatelle v0.7.2 // indirect github.com/ldez/usetesting v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/macabu/inamedparam v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect - github.com/manuelarte/funcorder v0.5.0 // indirect + github.com/manuelarte/funcorder v0.6.0 // indirect github.com/maratori/testableexamples v1.0.1 // indirect github.com/maratori/testpackage v1.1.2 // indirect github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mdlayher/netlink v1.7.2 // indirect - github.com/mdlayher/socket v0.5.1 // indirect github.com/mgechev/revive v1.15.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -219,14 +167,14 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/moricho/tparallel v0.3.2 // indirect - github.com/muesli/termenv v0.16.0 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.23.0 // indirect github.com/otiai10/copy v1.14.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pelletier/go-toml/v2 v2.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect @@ -241,17 +189,18 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect + github.com/ryancurrah/gomodguard/v2 v2.1.3 // indirect github.com/ryanrolds/sqlclosecheck v0.6.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect - github.com/securego/gosec/v2 v2.24.8-0.20260309165252-619ce2117e08 // indirect + github.com/securego/gosec/v2 v2.26.1 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sonatard/noctx v0.5.1 // indirect - github.com/sourcegraph/go-diff v0.7.0 // indirect + github.com/sourcegraph/go-diff v0.8.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.10.2 // indirect @@ -261,16 +210,17 @@ require ( github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/subosito/gotenv v1.4.1 // indirect - github.com/tetafro/godot v1.5.4 // indirect - github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect + github.com/tetafro/godot v1.5.6 // indirect + github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect github.com/timonwong/loggercheck v0.11.0 // indirect github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect github.com/uudashr/gocognit v1.2.1 // indirect - github.com/uudashr/iface v1.4.1 // indirect + github.com/uudashr/iface v1.4.2 // indirect github.com/xen0n/gosmopolitan v1.3.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yagipy/maintidx v1.0.0 // indirect @@ -278,32 +228,35 @@ require ( github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.14.0 // indirect - go-simpler.org/sloglint v0.11.1 // indirect + go-simpler.org/sloglint v0.12.0 // indirect go.augendre.info/arangolint v0.4.0 // indirect go.augendre.info/fatcontext v0.9.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/collector/cmd/builder v0.148.0 // indirect - go.opentelemetry.io/collector/component/componenttest v0.148.0 // indirect - go.opentelemetry.io/collector/confmap v1.54.0 // indirect - go.opentelemetry.io/collector/consumer v1.54.0 // indirect - go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect - go.opentelemetry.io/collector/featuregate v1.54.0 // indirect - go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect - go.opentelemetry.io/collector/pipeline v1.54.0 // indirect - go.opentelemetry.io/otel/sdk v1.42.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect - go.opentelemetry.io/otel/trace v1.42.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/collector/cmd/builder v0.152.0 // indirect + go.opentelemetry.io/collector/cmd/mdatagen v0.152.0 // indirect + go.opentelemetry.io/collector/component v1.58.0 // indirect + go.opentelemetry.io/collector/confmap v1.58.0 // indirect + go.opentelemetry.io/collector/confmap/provider/fileprovider v1.58.0 // indirect + go.opentelemetry.io/collector/featuregate v1.58.0 // indirect + go.opentelemetry.io/collector/filter v0.152.0 // indirect + go.opentelemetry.io/collector/internal/schemagen v0.152.0 // indirect + go.opentelemetry.io/collector/pdata v1.58.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.1 // indirect + go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect - golang.org/x/net v0.52.0 // indirect - golang.org/x/term v0.41.0 // indirect - golang.org/x/text v0.35.0 // indirect - golang.org/x/tools v0.43.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/tools v0.44.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tools.sum b/internal/tools/go.sum similarity index 79% rename from tools.sum rename to internal/tools/go.sum index a3d8838f9..2e7fd12e0 100644 --- a/tools.sum +++ b/internal/tools/go.sum @@ -2,6 +2,8 @@ 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= +charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= +charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -61,18 +63,20 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ClickHouse/clickhouse-go-linter v1.2.0 h1:zbm174up3hTKjp0wKZVnTzRiG7tSF5XZF0FJG/MuCBI= +github.com/ClickHouse/clickhouse-go-linter v1.2.0/go.mod h1:pLorS7ffPTfuUV9M0SJgfHA/h/WQPQUk2FWG9x74cQ4= github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/MirrexOne/unqueryvet v1.5.4 h1:38QOxShO7JmMWT+eCdDMbcUgGCOeJphVkzzRgyLJgsQ= github.com/MirrexOne/unqueryvet v1.5.4/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU= github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= -github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= +github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM= +github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= @@ -92,50 +96,10 @@ github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQ github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= -github.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo= -github.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c= -github.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE= -github.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY= -github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= -github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= -github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= -github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= -github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= -github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 h1:SwGMTMLIlvDNyhMteQ6r8IJSBPlRdXX5d4idhIGbkXA= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21/go.mod h1:UUxgWxofmOdAMuqEsSppbDtGKLfR04HGsD0HXzvhI1k= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 h1:qtJZ70afD3ISKWnoX3xB0J2otEqu3LqicRcDBqsj0hQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12/go.mod h1:v2pNpJbRNl4vEUWEh5ytQok0zACAKfdmKS51Hotc3pQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 h1:siU1A6xjUZ2N8zjTHSXFhB9L/2OY8Dqs0xXiLjF30jA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20/go.mod h1:4TLZCmVJDM3FOu5P5TJP0zOlu9zWgDWU7aUxWbr+rcw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2 h1:MRNiP6nqa20aEl8fQ6PJpEq11b2d40b16sm4WD7QgMU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2/go.mod h1:FrNA56srbsr3WShiaelyWYEo70x80mXnVZ17ZZfbeqg= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= -github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= -github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/ashanbrown/forbidigo/v2 v2.3.1 h1:KAZijvQ7zeIBKbhikT4jCm0TLYXC4u78bTiLh/8JROI= +github.com/ashanbrown/forbidigo/v2 v2.3.1/go.mod h1:2QDkLTzU6TV937eFROamXrW92M3paehdae4HCDCOZCM= +github.com/ashanbrown/makezero/v2 v2.2.1 h1:A7uU8dgB1PA9aelTxHMfHIQ8Qev8AB3JLxJUBUsejqM= +github.com/ashanbrown/makezero/v2 v2.2.1/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -148,14 +112,14 @@ github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= -github.com/bombsimon/wsl/v5 v5.6.0 h1:4z+/sBqC5vUmSp1O0mS+czxwH9+LKXtCWtHH9rZGQL8= -github.com/bombsimon/wsl/v5 v5.6.0/go.mod h1:Uqt2EfrMj2NV8UGoN1f1Y3m0NpUVCsUdrNCdet+8LvU= +github.com/bombsimon/wsl/v5 v5.8.0 h1:JTkyfs4yl8SPejrCF2GdABXE+mO1WvM7iUYzRWlsxDs= +github.com/bombsimon/wsl/v5 v5.8.0/go.mod h1:AbOLsulgkqP4ZnitHf9gwPtCOGlrzkk0jb0uNxRSY0o= github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= -github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E= -github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= +github.com/butuzov/ireturn v0.4.1 h1:vWb3NO4t77iku/sjCQ/2pHTQeOmxEhjIriJqRLg1Y+I= +github.com/butuzov/ireturn v0.4.1/go.mod h1:q+DXKzTDV5guNuXLnIab9fKXizTn2miZHLhxH7V/GB4= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ= @@ -169,24 +133,28 @@ 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/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk= github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= -github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= +github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI= +github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI= +github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= +github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= +github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.21.0 h1:4dpx1J/B/1apeTmWBH5BkVLayHTkFrMovVPnHEk+l3k= -github.com/cilium/ebpf v0.21.0/go.mod h1:1kHKv6Kvh5a6TePP5vvvoMa1bclRyzUXELSs272fmIQ= github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= @@ -203,14 +171,10 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= -github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= -github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= +github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= -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-20260224073651-af0ee0c731b7 h1:fGi5uudj7m5O1RgQl+bSZmwlqvuUMEi97X7TzWBOMGk= -github.com/elastic/go-perf v0.0.0-20260224073651-af0ee0c731b7/go.mod h1:ucTo2u8JvFyIPQOaRlX7aVF0d3wwmF1dy/PVp5GUHZI= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -225,8 +189,8 @@ github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47A github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= @@ -243,7 +207,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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= @@ -316,14 +279,14 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= +github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 h1:CbTB8KpqnViI6lIXxp03Oclc4VFHi3K4BWC1TacsZ+A= +github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U= github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint/v2 v2.11.4 h1:GK+UlZBN5y7rh2PBnHA93XLSX6RaF7uhzJQ3JwU1wuA= -github.com/golangci/golangci-lint/v2 v2.11.4/go.mod h1:ODQDCASMA3VqfZYIbbQLpTRTzV7O/vjmIRF6u8NyFwI= +github.com/golangci/golangci-lint/v2 v2.12.2 h1:7+d1uY0bq1MU2UV3R5pW5Q7QWdcoq4naMRXM+gsJKrs= +github.com/golangci/golangci-lint/v2 v2.12.2/go.mod h1:opqHHuIcTG2R+4akzWMd4o1BnD9/1LcjICWOujr91U8= github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0= github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10= github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg= @@ -332,6 +295,8 @@ github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3H github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba h1:lqtcnSMDuuJdu/LrKWi5RJzpSNLOJXYe/nzQutTI5kg= +github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba/go.mod h1:sCBNcpRmhJCtbFGz49+IM3ETTFf7QdJ30AeYCd43NKk= github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM= github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= @@ -377,8 +342,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= @@ -400,8 +363,8 @@ github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1T github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -415,20 +378,11 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jcchavezs/porto v0.7.0 h1:VncK84yxV7QZD4GdvoslzjnieSuruztGxLCmFi/Eu28= github.com/jcchavezs/porto v0.7.0/go.mod h1:tQ1cJ85cNzzZg/58VuZWOLbmrjcH1wPxkWgeBjvOq5o= -github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= -github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= -github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jgautheron/goconst v1.10.0 h1:Ptt+OoE4NaEWKhLrWrrN3IpZdGLiqaf7WLnEX/iv4Jw= +github.com/jgautheron/goconst v1.10.0/go.mod h1:0p+wv1lFOiUr0IlNNT1nrm6+8DB8u2sU6KHGzFRXHDc= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= -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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -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.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= -github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -447,10 +401,6 @@ github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= -github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= -github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4= @@ -463,8 +413,8 @@ github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= github.com/knadh/koanf/providers/fs v1.0.0 h1:tvn4MrduLgdOSUqqEHULUuIcELXf6xDOpH8GUErpYaY= github.com/knadh/koanf/providers/fs v1.0.0/go.mod h1:FksHET+xXFNDozvj8ZCdom54OnZ6eGKJtC5FhZJKx/8= -github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94= -github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= +github.com/knadh/koanf/v2 v2.3.4 h1:fnynNSDlujWE+v83hAp8wKr/cdoxHLO0629SN+U8Urc= +github.com/knadh/koanf/v2 v2.3.4/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -495,16 +445,16 @@ github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= +github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM= -github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8= -github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA= +github.com/manuelarte/funcorder v0.6.0 h1:0hBngc4fa1IgNiI65A7sFGkMvoMCc878RjqB5V7rWP0= +github.com/manuelarte/funcorder v0.6.0/go.mod h1:id3NDhXdQBmeqXH7eVC6Z89xS6JxvZ8kF9xUxpArU/g= github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8= github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= @@ -517,23 +467,12 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -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.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= -github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= -github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= -github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -552,8 +491,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= @@ -564,12 +503,10 @@ github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8= github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4= -github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= -github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= +github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= -github.com/open-telemetry/sig-profiling/tools/profcheck v0.0.0-20260303084341-52f633d434c9 h1:3NStK3r8FVhXbU0qkVz/DpPQlaoLLgLHJOAMKyDX4WM= -github.com/open-telemetry/sig-profiling/tools/profcheck v0.0.0-20260303084341-52f633d434c9/go.mod h1:KRO+Rec0+KycN1CrIP/6Pu0xOraPhbahCbL36i8FkfM= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -581,10 +518,8 @@ github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= -github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= +github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -625,7 +560,6 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4l github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -634,6 +568,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= +github.com/ryancurrah/gomodguard/v2 v2.1.3 h1:E7sz3PJwE9Ba1reVxSpF6XLCPJZ74Kfw/LabTNM4GIA= +github.com/ryancurrah/gomodguard/v2 v2.1.3/go.mod h1:CQicdLGatWMxLX53JzoBjYlsNZhHbmLv2AVa0s2aivU= github.com/ryanrolds/sqlclosecheck v0.6.0 h1:pEyL9okISdg1F1SEpJNlrEotkTGerv5BMk7U4AG0eVg= github.com/ryanrolds/sqlclosecheck v0.6.0/go.mod h1:xyX16hsDaCMXHrMJ3JMzGf5OpDfHTOTTQrT7HOFUmeU= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= @@ -644,13 +580,11 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= -github.com/securego/gosec/v2 v2.24.8-0.20260309165252-619ce2117e08 h1:AoLtJX4WUtZkhhUUMFy3GgecAALp/Mb4S1iyQOA2s0U= -github.com/securego/gosec/v2 v2.24.8-0.20260309165252-619ce2117e08/go.mod h1:+XLCJiRE95ga77XInNELh2M6zQP+PdqiT9Zpm0D9Wpk= +github.com/securego/gosec/v2 v2.26.1 h1:gdkttGhQFVehqRJ8grKH4DrpqM/QlPKNHBnl8QgcEC4= +github.com/securego/gosec/v2 v2.26.1/go.mod h1:57UW4p0uoP3kxoTkhoo3axLdVAi+OWrLg/Ax/kdqtPE= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -660,8 +594,8 @@ github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+W github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sonatard/noctx v0.5.1 h1:wklWg9c9ZYugOAk7qG4yP4PBrlQsmSLPTvW1K4PRQMs= github.com/sonatard/noctx v0.5.1/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= -github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= -github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/sourcegraph/go-diff v0.8.0 h1:ipIyu4cTsLbIrln4l0qtHA3r0a7gyK4ntKjtQytHhvY= +github.com/sourcegraph/go-diff v0.8.0/go.mod h1:hWlcO7Al+UZStZAP8rBumHpCK5ZHQ5BXsMls8p4+F5E= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -700,10 +634,10 @@ github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg= -github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= +github.com/tetafro/godot v1.5.6 h1:IEkrFCwXaYHlOn4mGzGS3F3dkP6m9t0jpwqBFPIkKiA= +github.com/tetafro/godot v1.5.6/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 h1:SiHe5XLTn9sFWJ5pBwJ5FN/4j34q9ZlOAD//kMoMYp0= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= @@ -716,8 +650,8 @@ github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSW github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4= github.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q= -github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= -github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= +github.com/uudashr/iface v1.4.2 h1:06Vq5RKVYThBsj0Bnw4oasMjD1r+7CE/bcKOA8dVSvg= +github.com/uudashr/iface v1.4.2/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= @@ -732,21 +666,16 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= -github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE= -go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s= -go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ= +go-simpler.org/sloglint v0.12.0 h1:UzWDlLWNE5FLqsvyq3tWYHuQMbqrervOhT8qPl4Mmw4= +go-simpler.org/sloglint v0.12.0/go.mod h1:jBjjC2bm8rYrs88oTRlFX497kWjJsyZWYoNaXkGRI6I= go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50= go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= @@ -760,58 +689,32 @@ 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.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/collector/cmd/builder v0.148.0 h1:a8mxjU7wyD1Qh9lj0UEfsNRL1/S1/yRDFuGkHIGlwbk= -go.opentelemetry.io/collector/cmd/builder v0.148.0/go.mod h1:pFwU1tJbcpfD+K37IsTboMQ5jsgPBj8nhtkXpO+1WLE= -go.opentelemetry.io/collector/component v1.54.0 h1:LvtX0Tzz18n44OrUFVk77N1FNsejfWJqztB28hrmDM8= -go.opentelemetry.io/collector/component v1.54.0/go.mod h1:yUMBYsySY/sDcXm8kOzEoZxt+JLdala6hxzSW0npOxY= -go.opentelemetry.io/collector/component/componenttest v0.148.0 h1:tBXJWmy2X6KD8S0QU2YZa2zYBqP+IycSM4iOtwDD2pA= -go.opentelemetry.io/collector/component/componenttest v0.148.0/go.mod h1:1c1+6mZOmI0raoya5vA/X0F+fawEjNS6tCEs5xLATtA= -go.opentelemetry.io/collector/confmap v1.54.0 h1:RUoxQ4uAYHTI57GfHh61D00tTQsXm9T88ozrAiicByc= -go.opentelemetry.io/collector/confmap v1.54.0/go.mod h1:mQxG8bk0IWIt9gbWMvzE+cRkOuCuzbzkNGBq2YJ4wNM= -go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 h1:UW8MX5VlKJf67x4Et7J9kPwP9Rv4VSmJ+UUpgRcb//c= -go.opentelemetry.io/collector/confmap/xconfmap v0.148.0/go.mod h1:4qTMr3V0uSXXac9wVs/UD5fIqRKw5yIl58+Vjsc6RHM= -go.opentelemetry.io/collector/consumer v1.54.0 h1:RGGtUN+GbkV1px3T6XdUHmgJ+ldJ1hAHdesFzW/wgL0= -go.opentelemetry.io/collector/consumer v1.54.0/go.mod h1:1PC6XINTL9DdT1bwvfMdHE72EB4RWU/WcPemUrhqKN8= -go.opentelemetry.io/collector/consumer/consumererror v0.148.0 h1:lKVkNWBeRXG41lHBf5KzA9oErRZifx6qTd9erAFfEkE= -go.opentelemetry.io/collector/consumer/consumererror v0.148.0/go.mod h1:N/UppmtknIdzpEiy3xirH1EiBEBOqKqD77NCyNi2Rbc= -go.opentelemetry.io/collector/consumer/consumertest v0.148.0 h1:ms0HtWMj17tI1Yds0hSuUI5QYpNEqd11AAhwIoUY2HE= -go.opentelemetry.io/collector/consumer/consumertest v0.148.0/go.mod h1:wScw/OzKkf/ZzJn4ToI30OoI1kJiY16WNrcFToXSzK0= -go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 h1:m3b9rY7CLD5Pcge6sSKHIT3OlcPN6xqYsdtVs9oJ528= -go.opentelemetry.io/collector/consumer/xconsumer v0.148.0/go.mod h1:bG+Wz6xmIBl/gHzq1sqvksWXqTLuTX17Wo//zIsdZpw= -go.opentelemetry.io/collector/featuregate v1.54.0 h1:ufo5Hy4Co9pcHVg24hyanm8qFG3TkkYbVyQXPVAbwDc= -go.opentelemetry.io/collector/featuregate v1.54.0/go.mod h1:PS7zY/zaCb28EqciePVwRHVhc3oKortTFXsi3I6ee4g= -go.opentelemetry.io/collector/internal/componentalias v0.148.0 h1:Y6MftNIZSzOr47TTj6A2z2UR3IwbeG46sAQshicGtDg= -go.opentelemetry.io/collector/internal/componentalias v0.148.0/go.mod h1:uwKzfehzwRgHxdHgFXYSBHNBeWSSqsqQYGWr5fk08G0= -go.opentelemetry.io/collector/internal/testutil v0.148.0 h1:3Z9hperte3vSmbBTYeNndoEUICICrNz8hzx+v0FYXBQ= -go.opentelemetry.io/collector/internal/testutil v0.148.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= -go.opentelemetry.io/collector/pdata v1.54.0 h1:3LharKb792cQ3VrUGxd3IcpWwfu3ST+GSTU382jVz1s= -go.opentelemetry.io/collector/pdata v1.54.0/go.mod h1:+MqC3VVOv/EX9YVFUo+mI4F0YmwJ+fXBYwjmu+mRiZ8= -go.opentelemetry.io/collector/pdata/pprofile v0.148.0 h1:MgrNZmqwhZGfiYwcKKtM/iXgTZqqvG5dUphriRXMZHU= -go.opentelemetry.io/collector/pdata/pprofile v0.148.0/go.mod h1:MTTMnZPqWX1S/rBDatU0W19udlycBkWuzVV5qnemHdc= -go.opentelemetry.io/collector/pdata/testdata v0.148.0 h1:yzakPuFgoKK8WcrlhyYHLMLA/kLScQKGsXkIgwieAQ8= -go.opentelemetry.io/collector/pdata/testdata v0.148.0/go.mod h1:2rFvxm8qwd3nlO90FtJw6ZGAjt+bLndxmQuJaMO9kfQ= -go.opentelemetry.io/collector/pipeline v1.54.0 h1:jYlCkdFLITVBdeB+IGS07zXWywEgvT3Ky46vdKKT+Ks= -go.opentelemetry.io/collector/pipeline v1.54.0/go.mod h1:RD90NG3Jbk965Xaqym3JyHkuol4uZJjQVUkD9ddXJIs= -go.opentelemetry.io/collector/receiver v1.54.0 h1:2e9o+eihZ/nJnzVj5JAcJ+VQ653HcZRiT127qBZRqa8= -go.opentelemetry.io/collector/receiver v1.54.0/go.mod h1:xFZnvYTBjdi9iS/d/UUXzss4h311mLsZliQFQXk4o/k= -go.opentelemetry.io/collector/receiver/receivertest v0.148.0 h1:Fu+B4jCqgZVZmhsKBz3tcgimFryR6TRAK2D5VGLD2Xc= -go.opentelemetry.io/collector/receiver/receivertest v0.148.0/go.mod h1:K8dMDMEggEg6jB688VOHutivOGEEZ20FJGe4jV9RtWU= -go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 h1:u66Zi3udD9RMRiNOsZzsVcUjRwqJEK+5LV76Ry9l3K0= -go.opentelemetry.io/collector/receiver/xreceiver v0.148.0/go.mod h1:jyHxf8SOfH48ZXb32IS3vPbVYDinsLlZYQddyrveqMg= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= -go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= -go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= -go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= -go.opentelemetry.io/proto/otlp/profiles/v1development v0.3.0 h1:ZQs05qo3Yh4KUHeVH6v89xErwmsvgA/cLX2/w5Ikp+k= -go.opentelemetry.io/proto/otlp/profiles/v1development v0.3.0/go.mod h1:3iiRVKaCfVo0UI1ZaSMm5WbCBbINRqVlD9SUmvyBNrY= +go.opentelemetry.io/collector/cmd/builder v0.152.0 h1:/EdXMZGUSIRhJxZzOrbCNiYes42+DR/uNHhe2xgNRP0= +go.opentelemetry.io/collector/cmd/builder v0.152.0/go.mod h1:5fiJ35EAPcas/kNW+YfQv5PcWzI/EDCyWgMs7FfY2f0= +go.opentelemetry.io/collector/cmd/mdatagen v0.152.0 h1:mDR9f7nb4YtsmgpydEJrIEL85Ftz8layoJd9k8+M4qo= +go.opentelemetry.io/collector/cmd/mdatagen v0.152.0/go.mod h1:UPaXSvvhmPGEuvdOKxOIlTTEBhrmsICwItyL6nKzyLE= +go.opentelemetry.io/collector/component v1.58.0 h1:GLHxFfw8jw3lRVX/h09Cc3inoR+2+XY93VxnrwHxY8Q= +go.opentelemetry.io/collector/component v1.58.0/go.mod h1:oAA7bLZUz/j1r16bQ9Cu/F+72xbQxlci8H2VmqtsAGE= +go.opentelemetry.io/collector/confmap v1.58.0 h1:lKk7XZ/BEA0eSlQWanBkhjDZewB/tu5EK2+PV/qlBws= +go.opentelemetry.io/collector/confmap v1.58.0/go.mod h1:2O/WadVBFwRzpO+3skcvjqDxD+OaS0TKKDDpPBaR4bs= +go.opentelemetry.io/collector/confmap/provider/fileprovider v1.58.0 h1:GTkUlglOc6e1DDXfGMZDti3s3DwpzS/EvGpXZMG0gQE= +go.opentelemetry.io/collector/confmap/provider/fileprovider v1.58.0/go.mod h1:anqn2j7ZGQZwsYA5dlc51Zu17GANAZaYRTWAwb7n7bg= +go.opentelemetry.io/collector/featuregate v1.58.0 h1:Kh6Dpgbxywv/Q3D6qPehaSxNCxvr/U/ki7CL4y3udCo= +go.opentelemetry.io/collector/featuregate v1.58.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= +go.opentelemetry.io/collector/filter v0.152.0 h1:v9TLF7p+FHrRAXcFtoVOQ9sleGoY1sdyKkUUf0c7Dmc= +go.opentelemetry.io/collector/filter v0.152.0/go.mod h1:GNDoOjdDk/HGvgzhUuGzyOtXRk8Xuv2CY+0+qay3QEA= +go.opentelemetry.io/collector/internal/schemagen v0.152.0 h1:Jv2JveUqo0EtigsFMjON69tLMp62nDg90lseRLo8rzc= +go.opentelemetry.io/collector/internal/schemagen v0.152.0/go.mod h1:EY67R+whkMGTvdbIT+YR7GrtzeKEVdMvDXhb4pLFND0= +go.opentelemetry.io/collector/internal/testutil v0.152.0 h1:8LGwekR7mLcUDhT1ofLmdnrHRFuUa3U7PBd95ZvJEjQ= +go.opentelemetry.io/collector/internal/testutil v0.152.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= +go.opentelemetry.io/collector/pdata v1.58.0 h1:5Lxut3NxKp87066Pzt+3q7+JUuFI5B3teCyLZIF8wIs= +go.opentelemetry.io/collector/pdata v1.58.0/go.mod h1:4vZtODINbC/JF3eGocnatdImzbRHseOywIcr+aULjCg= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM= go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk= go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ= @@ -822,14 +725,10 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= -go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE= -golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -837,8 +736,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U 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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -876,14 +773,11 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB 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.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= 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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -898,8 +792,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -917,16 +809,12 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -946,8 +834,6 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -955,7 +841,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -963,9 +848,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -987,9 +870,7 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w 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= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -999,19 +880,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1020,10 +895,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1071,14 +944,11 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -1087,8 +957,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1140,8 +1008,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1155,8 +1021,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= 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= diff --git a/internal/tools/tools.go b/internal/tools/tools.go new file mode 100644 index 000000000..cbb0e88f9 --- /dev/null +++ b/internal/tools/tools.go @@ -0,0 +1,2 @@ +// Package tools only tracks Go tools used by this project. +package tools // import "github.com/open-telemetry/opentelemetry-ebpf-profiler/internal/tools" diff --git a/interpreter/beam/beam.go b/interpreter/beam/beam.go index ab72f280f..47d392b39 100644 --- a/interpreter/beam/beam.go +++ b/interpreter/beam/beam.go @@ -337,7 +337,7 @@ func (d *beamData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libp func (d *beamData) Unload(_ interpreter.EbpfHandler) { } -func (i *beamInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler, _ reporter.ExecutableReporter, pr process.Process, mappings []process.Mapping) error { +func (i *beamInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler, _ reporter.ExecutableReporter, pr process.Process, mappings []process.RawMapping) error { pid := pr.PID() i.mappingGeneration++ for idx := range mappings { diff --git a/interpreter/dotnet/instance.go b/interpreter/dotnet/instance.go index c59304247..0da42883f 100644 --- a/interpreter/dotnet/instance.go +++ b/interpreter/dotnet/instance.go @@ -141,7 +141,7 @@ type dotnetInstance struct { virtualCallStubManagerManagerPtr libpf.Address // mappings contains the PE mappings to process memory space. Multiple individual - // consecutive process.Mappings may be merged to one mapping per PE file. + // consecutive process.RawMappings may be merged to one mapping per PE file. mappings []dotnetMapping ranges map[libpf.Address]dotnetRangeSection @@ -552,7 +552,7 @@ func (i *dotnetInstance) getDacSlotPtr(slot uint) libpf.Address { func (i *dotnetInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler, exeReporter reporter.ExecutableReporter, pr process.Process, - mappings []process.Mapping, + mappings []process.RawMapping, ) error { // get introspection data cdac, err := i.d.GetOrInit(func() (dotnetCdac, error) { return i.d.newVMData(i.rm, i.bias) }) @@ -599,7 +599,7 @@ func (i *dotnetInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler, if m.IsAnonymous() { continue } - if !strings.HasSuffix(m.Path.String(), ".dll") { + if !strings.HasSuffix(m.Path, ".dll") { continue } diff --git a/interpreter/dotnet/pe.go b/interpreter/dotnet/pe.go index 952a92e76..fde83feb4 100644 --- a/interpreter/dotnet/pe.go +++ b/interpreter/dotnet/pe.go @@ -1209,7 +1209,7 @@ func (pc *peCache) init() { pc.peInfoCache = peInfoCache } -func (pc *peCache) Get(pr process.Process, mapping *process.Mapping) *peInfo { +func (pc *peCache) Get(pr process.Process, mapping *process.RawMapping) *peInfo { key := mapping.GetOnDiskFileIdentifier() lastModified := pr.GetMappingFileLastModified(mapping) if info, ok := pc.peInfoCache.Get(key); ok && info.lastModified == lastModified { @@ -1243,7 +1243,7 @@ func (pc *peCache) Get(pr process.Process, mapping *process.Mapping) *peInfo { if info.err == nil { mf := libpf.NewFrameMappingFile(libpf.FrameMappingFileData{ FileID: fileID, - FileName: libpf.Intern(path.Base(mapping.Path.String())), + FileName: libpf.Intern(path.Base(mapping.Path)), GnuBuildID: info.guid, }) info.mapping = libpf.NewFrameMapping(libpf.FrameMappingData{ diff --git a/interpreter/go/go.go b/interpreter/go/go.go index 0cde865dc..ff5d54fbf 100644 --- a/interpreter/go/go.go +++ b/interpreter/go/go.go @@ -7,6 +7,7 @@ import ( "fmt" "sync/atomic" + "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/metrics" @@ -24,6 +25,7 @@ var ( type goData struct { refs atomic.Int32 + fileID host.FileID version string pclntab *elfunwindinfo.Gopclntab @@ -56,6 +58,7 @@ func Loader(_ interpreter.EbpfHandler, info *interpreter.LoaderInfo) ( } g := &goData{ + fileID: info.FileID(), version: goVersion, pclntab: pclntab, } @@ -105,6 +108,10 @@ func (g *goInstance) Symbolize(ef libpf.EbpfFrame, frames *libpf.Frames, mapping if !ef.Type().IsInterpType(libpf.Native) { return interpreter.ErrMismatchInterpreterType } + // Skip native frames that do not belong to this Go binary. + if host.FileID(ef.Variable(0)) != g.d.fileID { + return interpreter.ErrMismatchInterpreterType + } sfCounter := successfailurecounter.New(&g.successCount, &g.failCount) defer sfCounter.DefaultToFailure() diff --git a/interpreter/go/go_test.go b/interpreter/go/go_test.go index b9190006a..e6d4cdc94 100644 --- a/interpreter/go/go_test.go +++ b/interpreter/go/go_test.go @@ -49,7 +49,7 @@ func BenchmarkGolang(b *testing.B) { } frames := libpf.Frames{} - ef := libpf.NewEbpfFrame(libpf.NativeFrame, 0, 1, uint64(pc)) + ef := libpf.NewEbpfFrame(libpf.NativeFrame, 0, 2, uint64(pc)) ef[1] = uint64(hostFileID) if err := gI.Symbolize(ef, &frames, libpf.FrameMapping{}); err != nil { diff --git a/interpreter/golabels/golabels.go b/interpreter/golabels/golabels.go index 71e00e284..1e733d0a6 100644 --- a/interpreter/golabels/golabels.go +++ b/interpreter/golabels/golabels.go @@ -4,6 +4,7 @@ package golabels // import "go.opentelemetry.io/ebpf-profiler/interpreter/golabels" import ( + "debug/elf" "errors" "fmt" "go/version" @@ -59,6 +60,25 @@ func Loader(_ interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interprete return nil, nil } + // Go plugins are shared objects that share the runtime with the main + // binary. The offsets we need are determined by the main binary so + // there is no reason to create a duplicate golabels instance for + // a plugin. A shared library is ET_DYN without a PT_INTERP segment + // (PIE executables are also ET_DYN but have PT_INTERP). + if file.Type == elf.ET_DYN { + hasInterp := false + for i := range file.Progs { + if file.Progs[i].Type == elf.PT_INTERP { + hasInterp = true + break + } + } + if !hasInterp { + log.Debugf("file %s is a Go shared library, skipping golabels", info.FileName()) + return nil, nil + } + } + if version.Compare(goVersion, "go1.27") >= 0 { return nil, fmt.Errorf("unsupported Go version %s (need >= 1.13 and <= 1.26)", goVersion) } diff --git a/interpreter/hotspot/instance.go b/interpreter/hotspot/instance.go index ce0802f68..e116b772e 100644 --- a/interpreter/hotspot/instance.go +++ b/interpreter/hotspot/instance.go @@ -854,7 +854,7 @@ func (d *hotspotInstance) updateStubMappings(vmd *hotspotVMData, } func (d *hotspotInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler, - _ reporter.ExecutableReporter, pr process.Process, _ []process.Mapping, + _ reporter.ExecutableReporter, pr process.Process, _ []process.RawMapping, ) error { vmd, err := d.d.GetOrInit(func() (hotspotVMData, error) { return d.d.newVMData(d.rm, d.bias) }) if err != nil { diff --git a/interpreter/instancestubs.go b/interpreter/instancestubs.go index 662eb74d8..1eca53dea 100644 --- a/interpreter/instancestubs.go +++ b/interpreter/instancestubs.go @@ -17,7 +17,7 @@ type InstanceStubs struct { } func (is *InstanceStubs) SynchronizeMappings(EbpfHandler, reporter.ExecutableReporter, - process.Process, []process.Mapping) error { + process.Process, []process.RawMapping) error { return nil } diff --git a/interpreter/multi.go b/interpreter/multi.go index 08634384b..d976228d2 100644 --- a/interpreter/multi.go +++ b/interpreter/multi.go @@ -92,7 +92,7 @@ func (m *MultiInstance) Detach(ebpf EbpfHandler, pid libpf.PID) error { // SynchronizeMappings synchronizes mappings for all interpreter instances. func (m *MultiInstance) SynchronizeMappings(ebpf EbpfHandler, - exeReporter reporter.ExecutableReporter, pr process.Process, mappings []process.Mapping, + exeReporter reporter.ExecutableReporter, pr process.Process, mappings []process.RawMapping, ) error { var errs []error for _, instance := range m.instances { diff --git a/interpreter/nodev8/v8.go b/interpreter/nodev8/v8.go index 12cffa30a..edb4a3cc7 100644 --- a/interpreter/nodev8/v8.go +++ b/interpreter/nodev8/v8.go @@ -500,8 +500,8 @@ type v8Instance struct { addrToSource *freelru.LRU[libpf.Address, *v8Source] addrToType *freelru.LRU[libpf.Address, uint16] - // mappings is indexed by the Mapping to its generation - mappings map[process.Mapping]*uint32 + // mappings is indexed by the RawMapping to its generation + mappings map[process.RawMapping]*uint32 // prefixes is indexed by the prefix added to ebpf maps (to be cleaned up) to its generation prefixes map[lpm.Prefix]*uint32 // mappingGeneration is the current generation (so old entries can be pruned) @@ -555,7 +555,7 @@ func (i *v8Instance) Detach(ebpf interpreter.EbpfHandler, pid libpf.PID) error { } func (i *v8Instance) SynchronizeMappings(ebpf interpreter.EbpfHandler, - _ reporter.ExecutableReporter, pr process.Process, mappings []process.Mapping, + _ reporter.ExecutableReporter, pr process.Process, mappings []process.RawMapping, ) error { pid := pr.PID() i.mappingGeneration++ @@ -1114,9 +1114,13 @@ func (i *v8Instance) getSFI(taggedPtr libpf.Address) (*v8SFI, error) { } else { log.Debugf("Bytecode, %d bytes, not available", length) } + typ := vms.Type.ByteArray + if vms.SourcePositionTable.TrustedByteArray { + typ = vms.Type.TrustedByteArray + } sfi.bytecodePositionTable, err = i.readFixedTablePtr( fdAddr+libpf.Address(vms.BytecodeArray.SourcePositionTable), - vms.Type.ByteArray, 1, 0) + typ, 1, 0) log.Debugf("Bytecode positions: %d bytes: %v", len(sfi.bytecodePositionTable), err) } @@ -1841,7 +1845,7 @@ func (d *v8Data) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, _ libpf.Add return &v8Instance{ d: d, rm: rm, - mappings: make(map[process.Mapping]*uint32), + mappings: make(map[process.RawMapping]*uint32), prefixes: make(map[lpm.Prefix]*uint32), addrToString: addrToString, addrToCode: addrToCode, @@ -2248,7 +2252,7 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr sym.Address, sym.Size, sym.Size/3) d.bytecodeSizes = make([]byte, sym.Size) d.bytecodeCount = uint8(sym.Size / 3) - if _, err = ef.ReadVirtualMemory(d.bytecodeSizes, int64(sym.Address)); err != nil { + if _, err = ef.ReadAt(d.bytecodeSizes, int64(sym.Address)); err != nil { return nil, fmt.Errorf("unable to read bytecode sizes: %v", err) } for _, opcodeLength := range d.bytecodeSizes { diff --git a/interpreter/perl/data.go b/interpreter/perl/data.go index 294342c27..3648a143e 100644 --- a/interpreter/perl/data.go +++ b/interpreter/perl/data.go @@ -162,7 +162,7 @@ func newData(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo, for i, sym := range []libpf.SymbolName{"PL_revision", "PL_version", "PL_subversion"} { addr, err := ef.LookupSymbolAddress(sym) if err == nil { - _, err = ef.ReadVirtualMemory(verBytes[i:i+1], int64(addr)) + _, err = ef.ReadAt(verBytes[i:i+1], int64(addr)) } if err != nil { return nil, fmt.Errorf("perl symbol '%s': %v", sym, err) diff --git a/interpreter/php/opcache.go b/interpreter/php/opcache.go index 380e05120..2e8f5afdb 100644 --- a/interpreter/php/opcache.go +++ b/interpreter/php/opcache.go @@ -193,7 +193,7 @@ func (i *opcacheInstance) Detach(ebpf interpreter.EbpfHandler, pid libpf.PID) er } func (i *opcacheInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler, - _ reporter.ExecutableReporter, pr process.Process, _ []process.Mapping, + _ reporter.ExecutableReporter, pr process.Process, _ []process.RawMapping, ) error { if i.prefixes != nil { // Already attached diff --git a/interpreter/python/python.go b/interpreter/python/python.go index ba52263bd..8ebc55b35 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "path" "reflect" "regexp" "slices" @@ -48,6 +49,23 @@ func pythonVer(major, minor uint16) uint16 { return major*0x100 + minor } +func readPyVersionHex(ef *pfelf.File) (major uint8, minor uint8, err error) { + // Py_Version is referenced in CPython internals for versioned Python binaries. + // https://github.com/python/cpython/blob/v3.11.0/Doc/c-api/apiabiversion.rst + addr, err := ef.LookupSymbolAddress("Py_Version") + if err != nil { + return 0, 0, err + } + rm := ef.GetRemoteMemory() + versionHex := rm.Uint32(libpf.Address(addr)) + major = uint8((versionHex >> 24) & 0xff) + minor = uint8((versionHex >> 16) & 0xff) + if major == 0 { + return 0, 0, fmt.Errorf("invalid Py_Version 0x%x", versionHex) + } + return major, minor, nil +} + //nolint:lll type pythonData struct { version uint16 @@ -483,10 +501,13 @@ func (p *pythonInstance) getCodeObject(addr libpf.Address, if addr == 0 { return nil, errors.New("failed to read code object: null pointer") } - if value, ok := p.addrToCodeObject.Get(addr); ok { - m := value - if m.ebpfChecksum == ebpfChecksum { - return m, nil + if ebpfChecksum != 0 { + // A zero checksum indicates code object read failed in the kernel (e.g. paged out). + if value, ok := p.addrToCodeObject.Get(addr); ok { + m := value + if m.ebpfChecksum == ebpfChecksum { + return m, nil + } } } @@ -541,7 +562,7 @@ func (p *pythonInstance) getCodeObject(addr libpf.Address, ebpfChecksumCalculated := (argCount << 25) + (kwonlyArgCount << 18) + (flags << 10) + firstLineNo - if ebpfChecksum != ebpfChecksumCalculated { + if ebpfChecksum != 0 && ebpfChecksum != ebpfChecksumCalculated { return nil, fmt.Errorf("read code object was stale: %x != %x", ebpfChecksum, ebpfChecksumCalculated) } @@ -562,7 +583,7 @@ func (p *pythonInstance) getCodeObject(addr libpf.Address, sourceFileName: libpf.Intern(sourceFileName), firstLineNo: firstLineNo, lineTable: lineTable, - ebpfChecksum: ebpfChecksum, + ebpfChecksum: ebpfChecksumCalculated, } p.addrToCodeObject.Add(addr, pco) return pco, nil @@ -721,19 +742,36 @@ func decodeStub(ef *pfelf.File, memoryBase libpf.SymbolValue, func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) { mainDSO := false + major := uint16(0) + minor := uint16(0) matches := libpythonRegex.FindStringSubmatch(info.FileName()) if matches == nil { mainDSO = true matches = pythonRegex.FindStringSubmatch(info.FileName()) - if matches == nil { + } + if matches == nil { + if !strings.HasPrefix(path.Base(info.FileName()), "python") { return nil, nil } + } else { + majorValue, _ := strconv.ParseUint(matches[1], 10, 16) + minorValue, _ := strconv.ParseUint(matches[2], 10, 16) + major = uint16(majorValue) + minor = uint16(minorValue) } ef, err := info.GetELF() if err != nil { return nil, err } + if major == 0 { + majorFromSym, minorFromSym, versionErr := readPyVersionHex(ef) + if versionErr != nil { + return nil, nil + } + major = uint16(majorFromSym) + minor = uint16(minorFromSym) + } if mainDSO { var needed []string @@ -749,9 +787,7 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr } var pyruntimeAddr, autoTLSKey libpf.SymbolValue - major, _ := strconv.ParseUint(matches[1], 10, 16) - minor, _ := strconv.ParseUint(matches[2], 10, 16) - version := pythonVer(uint16(major), uint16(minor)) + version := pythonVer(major, minor) minVer := pythonVer(3, 6) maxVer := pythonVer(3, 14) diff --git a/interpreter/ruby/ruby.go b/interpreter/ruby/ruby.go index ba885d2b0..445011690 100644 --- a/interpreter/ruby/ruby.go +++ b/interpreter/ruby/ruby.go @@ -21,6 +21,7 @@ import ( "github.com/elastic/go-freelru" "go.opentelemetry.io/ebpf-profiler/interpreter" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/libpf/pfunsafe" @@ -129,6 +130,13 @@ type rubyData struct { // extracted from disassembly of rb_current_ec_noinline staticTLSOffset int64 + // For DTV-based TLS access: offset of ruby_current_ec within its TLS block + currentEcTlsOffset libpf.Address + + // For DTV-based TLS access: ELF offset where the TLS module ID is stored + // (from DTPMOD64 relocation, the actual module ID is written by the linker at load time) + tlsModuleIdOffset libpf.Address + // Address to global symbols, for id to string mappings globalSymbolsAddr libpf.Address // version of the currently used Ruby interpreter. @@ -311,11 +319,21 @@ func (r *rubyData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libp tlsOffset = int64(rm.Uint64(bias + r.currentEcTpBaseTlsOffset + 8)) } + // For DTV-based access: read the actual module ID from process memory. + // The linker writes the module ID at the relocation offset at load time. + var modID uint32 + if r.tlsModuleIdOffset != 0 { + modID = uint32(rm.Uint64(bias + r.tlsModuleIdOffset)) + log.Debugf("Ruby TLS module ID: %d", modID) + } + cdata := support.RubyProcInfo{ Version: r.version, Current_ctx_ptr: uint64(r.currentCtxPtr + bias), Current_ec_tpbase_tls_offset: tlsOffset, + Current_ec_tls_offset: uint64(r.currentEcTlsOffset), + Tls_module_id: modID, Vm_stack: r.vmStructs.execution_context_struct.vm_stack, Vm_stack_size: r.vmStructs.execution_context_struct.vm_stack_size, @@ -355,6 +373,7 @@ func (r *rubyData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libp return &rubyInstance{ r: r, rm: rm, + procInfo: &cdata, globalSymbolsAddr: r.globalSymbolsAddr + bias, addrToString: addrToString, memPool: sync.Pool{ @@ -391,6 +410,12 @@ type rubyIseq struct { type rubyInstance struct { interpreter.InstanceStubs + // procInfo stores the eBPF proc data for re-insertion when UpdateLibcInfo provides DTVInfo + procInfo *support.RubyProcInfo + + // dtvInfoInserted tracks whether we have already updated procInfo with DTVInfo + dtvInfoInserted bool + // Ruby symbolization metrics successCount atomic.Uint64 failCount atomic.Uint64 @@ -418,6 +443,33 @@ func (r *rubyInstance) Detach(ebpf interpreter.EbpfHandler, pid libpf.PID) error return ebpf.DeleteProcData(libpf.Ruby, pid) } +// UpdateLibcInfo is called when libc introspection data becomes available. +// Ruby uses this to receive DTVInfo for DTV-based TLS access to ruby_current_ec +// when TLSDESC relocations are unavailable. +func (r *rubyInstance) UpdateLibcInfo(ebpf interpreter.EbpfHandler, pid libpf.PID, + libcInfo libc.LibcInfo) error { + // Only need DTVInfo if we're using DTV-based access (have a module ID but no TLSDESC offset) + if r.procInfo.Tls_module_id == 0 { + return nil + } + if !libcInfo.HasDTVInfo() { + // DTV info not available yet (may arrive from a different DSO) + return nil + } + if r.dtvInfoInserted { + return nil + } + + r.procInfo.Dtv_info = libcInfo.DTVInfo + if err := ebpf.UpdateProcData(libpf.Ruby, pid, unsafe.Pointer(r.procInfo)); err != nil { + return err + } + r.dtvInfoInserted = true + log.Debugf("Ruby: updated proc data with DTVInfo (offset=%d, multiplier=%d)", + libcInfo.DTVInfo.Offset, libcInfo.DTVInfo.Multiplier) + return nil +} + // readRubyArrayDataPtr obtains the data pointer of a Ruby array (RArray). // // https://github.com/ruby/ruby/blob/95aff2146/include/ruby/internal/core/rarray.h#L87 @@ -1387,13 +1439,26 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr } } - log.Debugf("Discovered EC tls tpbase offset %x, static tls offset %d, fallback ctx %x, interp ranges: %v, global symbols: %x", - currentEcTpBaseTlsOffset, staticTLSOffset, currentCtxPtr, interpRanges, globalSymbols) + // Look for DTPMOD64 relocation to find the TLS module ID offset. + // This is used for DTV-based TLS access when TLSDESC is unavailable. + var tlsModuleIdOffset libpf.Address + if err = ef.VisitRelocations(func(r pfelf.ElfReloc, _ string) bool { + log.Debugf("Found DTPMOD64 relocation at offset %x", r.Off) + tlsModuleIdOffset = libpf.Address(r.Off) + return false + }, pfelf.RelDTPMOD64); err != nil { + log.Warnf("failed to find DTPMOD64 relocation: %v", err) + } + + log.Debugf("Discovered EC tls tpbase offset %x, static tls offset %d, dtpmod offset %x, fallback ctx %x, interp ranges: %v, global symbols: %x", + currentEcTpBaseTlsOffset, staticTLSOffset, tlsModuleIdOffset, currentCtxPtr, interpRanges, globalSymbols) rid := &rubyData{ version: version, currentEcTpBaseTlsOffset: libpf.Address(currentEcTpBaseTlsOffset), staticTLSOffset: staticTLSOffset, + currentEcTlsOffset: libpf.Address(currentEcSymbolAddress), + tlsModuleIdOffset: tlsModuleIdOffset, currentCtxPtr: libpf.Address(currentCtxPtr), hasGlobalSymbols: globalSymbols != 0, globalSymbolsAddr: libpf.Address(globalSymbols), diff --git a/interpreter/types.go b/interpreter/types.go index 991ec65a8..4b2e219c2 100644 --- a/interpreter/types.go +++ b/interpreter/types.go @@ -137,11 +137,21 @@ type Instance interface { // simple interpreters can use the global Data also as the Instance implementation. Detach(ebpf EbpfHandler, pid libpf.PID) error - // SynchronizeMappings is called when the processmanager has reread process memory - // mappings. Interpreters not needing to process these events can simply ignore them - // by just returning a nil. + // SynchronizeMappings is called when the processmanager has reread process + // memory mappings. The mappings slice contains only the subset of mappings + // that are relevant to interpreters: executable anonymous mappings (for JIT + // engines like HotSpot, V8, BEAM) and DLL file-backed mappings (for .NET + // PE assemblies). The processmanager decides which mappings are included; + // this can be made more dynamic in the future if needed. + // + // The mappings are in /proc/PID/maps order (ascending by virtual address) + // but are NOT sorted by any other criteria. Interpreters that need a + // specific ordering must sort locally. + // + // Interpreters not needing to process these events can simply ignore them + // by returning nil. SynchronizeMappings(ebpf EbpfHandler, exeReporter reporter.ExecutableReporter, - pr process.Process, mappings []process.Mapping) error + pr process.Process, mappings []process.RawMapping) error // UpdateLibcInfo is called when the process C-library related // introspection data has been updated. diff --git a/kallsyms/bpf.go b/kallsyms/bpf.go new file mode 100644 index 000000000..c34c32438 --- /dev/null +++ b/kallsyms/bpf.go @@ -0,0 +1,349 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package kallsyms // import "go.opentelemetry.io/ebpf-profiler/kallsyms" + +import ( + "cmp" + "context" + "errors" + "slices" + "strings" + "sync/atomic" + "time" + + "github.com/cilium/ebpf" + "github.com/elastic/go-perf" + "go.opentelemetry.io/ebpf-profiler/internal/log" + "go.opentelemetry.io/ebpf-profiler/libpf" + "golang.org/x/sys/unix" +) + +// bpfProgPrefix is the prefix the kernel uses for all JIT'd BPF program +// symbols in /proc/kallsyms and PERF_RECORD_KSYMBOL events. +const bpfProgPrefix = "bpf_prog_" + +type bpfSymbol struct { + address libpf.Address + size uint32 + name string +} + +// bpfSymbolTable is a sorted (by address) snapshot of all known BPF program +// symbols. It is stored atomically so readers never block writers. +type bpfSymbolTable struct { + symbols []bpfSymbol +} + +// lookup returns the symbol containing addr, or ("", false) if none does. +// A symbol covers [address, address+size). +func (t *bpfSymbolTable) lookup(addr libpf.Address) (string, uint, bool) { + // Binary search for the last symbol whose address <= addr. + // BinarySearchFunc returns (index of exact match, true) or + // (insertion point, false). In both cases the candidate symbol + // is at the returned index when found, or at index-1 when not found. + idx, found := slices.BinarySearchFunc(t.symbols, addr, func(sym bpfSymbol, a libpf.Address) int { + return cmp.Compare(sym.address, a) + }) + + if !found { + // idx is the insertion point; the last symbol with address <= addr + // is one position to the left. + if idx == 0 { + return "", 0, false + } + idx-- + } + + sym := &t.symbols[idx] + if addr >= sym.address+libpf.Address(sym.size) { + return "", 0, false + } + + return sym.name, uint(addr - sym.address), true +} + +// bpfSymbolizer is responsible for getting updates from `PERF_RECORD_KSYMBOL`. +// The symbolizer is not ready to use until startMonitor is called to load the symbols. +type bpfSymbolizer struct { + records chan *perf.KSymbolRecord + events []*perf.Event + cancel context.CancelFunc + table atomic.Pointer[bpfSymbolTable] +} + +// LookupSymbol resolves addr to a BPF program symbol name and offset. +// Returns ("", 0, false) if no BPF program covers addr. +func (s *bpfSymbolizer) LookupSymbol(addr libpf.Address) (string, uint, bool) { + t := s.table.Load() + if t == nil { + return "", 0, false + } + + return t.lookup(addr) +} + +// loadBPFPrograms enumerates all loaded BPF programs via the bpf syscall and +// builds a sorted bpfSymbolTable from their JIT symbol addresses and sizes. +// Only symbols with the "bpf_prog_" prefix are included; trampolines and +// dispatchers are intentionally excluded because they are not visible at +// initial scan time and would cause misattribution. +func (s *bpfSymbolizer) loadBPFPrograms() error { + symbols := []bpfSymbol{} + + id := ebpf.ProgramID(0) + for { + var err error + id, err = ebpf.ProgramGetNextID(id) + if err != nil { + break + } + + prog, err := ebpf.NewProgramFromID(id) + if err != nil { + // Program may have been unloaded between listing and opening. + continue + } + + info, err := prog.Info() + prog.Close() + if err != nil { + continue + } + + addrs, ok := info.JitedKsymAddrs() + if !ok || len(addrs) == 0 { + continue + } + + lens, _ := info.JitedFuncLens() + + // The kernel names BPF JIT symbols as "bpf_prog__". + name := bpfProgPrefix + info.Tag + "_" + info.Name + + for i, addr := range addrs { + sym := bpfSymbol{ + address: libpf.Address(addr), + name: name, + } + + if i < len(lens) { + sym.size = lens[i] + } + + symbols = append(symbols, sym) + } + } + + slices.SortFunc(symbols, func(a, b bpfSymbol) int { + return cmp.Compare(a.address, b.address) + }) + + s.table.Store(&bpfSymbolTable{symbols: symbols}) + + return nil +} + +// startMonitor starts the update monitoring and loads bpf symbols. +func (s *bpfSymbolizer) startMonitor(ctx context.Context, onlineCPUs []int) error { + ctx, s.cancel = context.WithCancel(ctx) + + err := s.subscribe(ctx, onlineCPUs) + if err != nil { + return err + } + + err = s.loadBPFPrograms() + if err != nil { + return err + } + + go s.reloadWorker(ctx) + + return nil +} + +// subscribe subscribes to updates for bpf symbols via `PERF_RECORD_KSYMBOL`. +func (s *bpfSymbolizer) subscribe(ctx context.Context, onlineCPUs []int) error { + attr := new(perf.Attr) + perf.Dummy.Configure(attr) + attr.Options.KSymbol = true + attr.SetWakeupWatermark(1) + + s.records = make(chan *perf.KSymbolRecord) + + for _, cpu := range onlineCPUs { + event, err := perf.Open(attr, perf.AllThreads, cpu, nil) + if err != nil { + return err + } + + s.events = append(s.events, event) + + err = event.MapRing() + if err != nil { + return err + } + + err = event.Enable() + if err != nil { + return err + } + + go func(event *perf.Event) { + for { + record, err := event.ReadRecord(ctx) + if err != nil { + if ctx.Err() != nil { + return + } + + log.Errorf("Failed to read perf event: %v", err) + continue + } + + switch ksymbol := record.(type) { + case *perf.LostRecord: + // nil as a sentinel value to indicate lost events. Whenever this happens + // we trigger a full re-scan of existing bpf programs to prevent data loss. + select { + case s.records <- nil: + case <-ctx.Done(): + } + case *perf.KSymbolRecord: + if ksymbol.Type != unix.PERF_RECORD_KSYMBOL_TYPE_BPF { + continue + } + + select { + case s.records <- ksymbol: + case <-ctx.Done(): + } + default: + log.Debugf("Unexpected perf record type: %T", record) + } + + if ctx.Err() != nil { + return + } + } + }(event) + } + + return nil +} + +// reloadWorker is the goroutine handling the reloads of the bpf symbols. +func (s *bpfSymbolizer) reloadWorker(ctx context.Context) { + noTimeout := make(<-chan time.Time) + nextReload := noTimeout + for { + select { + case <-nextReload: + if err := s.loadBPFPrograms(); err == nil { + log.Debugf("Kernel symbols reloaded") + nextReload = noTimeout + } else { + log.Warnf("Failed to reload kernel symbols: %v", err) + nextReload = time.After(time.Second) + } + case record := <-s.records: + if err := s.handleBPFUpdate(record); err != nil { + log.Warnf("Error handling bpf ksymbol update: %v", err) + nextReload = time.After(time.Second) + } + case <-ctx.Done(): + return + } + } +} + +// handleBPFUpdate handles the update record from perf events. +func (s *bpfSymbolizer) handleBPFUpdate(record *perf.KSymbolRecord) error { + if record == nil { + return errors.New("lost events detected") + } + + // Only track bpf_prog_* symbols. Trampolines, dispatchers, and other + // BPF-tagged symbols are excluded because they are not present at initial + // scan time and would cause misattribution. + if !strings.HasPrefix(record.Name, bpfProgPrefix) { + return nil + } + + if record.Flags&unix.PERF_RECORD_KSYMBOL_FLAGS_UNREGISTER != 0 { + s.removeBPFSymbol(libpf.Address(record.Addr)) + return nil + } + + s.addBPFSymbol(libpf.Address(record.Addr), record.Name, record.Len) + + return nil +} + +// addBPFSymbol inserts a new BPF program symbol into the table. +func (s *bpfSymbolizer) addBPFSymbol(addr libpf.Address, name string, size uint32) { + old := s.table.Load() + var oldSymbols []bpfSymbol + if old != nil { + oldSymbols = old.symbols + } + + // Check for a benign race: symbol already present with the same name. + idx, found := slices.BinarySearchFunc(oldSymbols, addr, func(sym bpfSymbol, a libpf.Address) int { + return cmp.Compare(sym.address, a) + }) + if found && oldSymbols[idx].name == name { + return + } + + // Insert the new symbol into the right position to maintain sorting. + newSym := bpfSymbol{address: addr, size: size, name: name} + newSymbols := make([]bpfSymbol, len(oldSymbols)+1) + copy(newSymbols, oldSymbols[:idx]) + newSymbols[idx] = newSym + copy(newSymbols[idx+1:], oldSymbols[idx:]) + + s.table.Store(&bpfSymbolTable{symbols: newSymbols}) +} + +// removeBPFSymbol removes a BPF program symbol from the table by address. +func (s *bpfSymbolizer) removeBPFSymbol(addr libpf.Address) { + old := s.table.Load() + if old == nil { + return + } + + idx, found := slices.BinarySearchFunc(old.symbols, addr, func(sym bpfSymbol, a libpf.Address) int { + return cmp.Compare(sym.address, a) + }) + if !found { + return + } + + newSymbols := make([]bpfSymbol, len(old.symbols)-1) + copy(newSymbols, old.symbols[:idx]) + copy(newSymbols[idx:], old.symbols[idx+1:]) + + s.table.Store(&bpfSymbolTable{symbols: newSymbols}) +} + +// Close frees resources associated with bpfSymbolizer. +func (s *bpfSymbolizer) Close() { + // Cancel the context first so reader goroutines and reloadWorker + // observe ctx.Done() and exit before we close the perf events. + if s.cancel != nil { + s.cancel() + } + + for _, event := range s.events { + if err := event.Disable(); err != nil { + log.Errorf("Failed to disable perf event: %v", err) + } + if err := event.Close(); err != nil { + log.Errorf("Failed to close perf event: %v", err) + } + } + + s.events = nil +} diff --git a/kallsyms/bpf_integration_test.go b/kallsyms/bpf_integration_test.go new file mode 100644 index 000000000..850678227 --- /dev/null +++ b/kallsyms/bpf_integration_test.go @@ -0,0 +1,179 @@ +//go:build integration && linux + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package kallsyms + +import ( + "runtime" + "strings" + "testing" + "time" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/ebpf-profiler/libpf" + "go.opentelemetry.io/ebpf-profiler/rlimit" +) + +const ( + eventuallyWaitFor = 10 * time.Second + eventuallyTick = 100 * time.Millisecond + + dynamicProgName = "otel_dyn_test" + preexistingProgName = "otel_pre_test" +) + +// linearCPUs returns []int{0, 1, ..., n-1} for n online CPUs. +// This assumes contiguous CPU IDs, which is practical for integration tests. +// The proper parsing of /sys/devices/system/cpu/online lives in tracer/helper.go, +// but we don't want to export or duplicate it here. +func linearCPUs() []int { + cpus := make([]int, runtime.NumCPU()) + for i := range cpus { + cpus[i] = i + } + return cpus +} + +// loadSocketFilter loads a minimal BPF socket filter program with the given name. +// The program simply returns 0. The caller is responsible for closing it. +func loadSocketFilter(t *testing.T, name string) *ebpf.Program { + t.Helper() + + spec := &ebpf.ProgramSpec{ + Name: name, + Type: ebpf.SocketFilter, + License: "GPL", + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + } + + prog, err := ebpf.NewProgram(spec) + require.NoError(t, err) + + return prog +} + +// findBPFSymbol searches the bpfSymbolTable for a symbol whose name ends with +// "_". Returns the full symbol name and its address. +func findBPFSymbol(s *bpfSymbolizer, progName string) (string, libpf.Address) { + suffix := "_" + progName + + tbl := s.table.Load() + if tbl == nil { + return "", 0 + } + + for _, sym := range tbl.symbols { + if strings.HasSuffix(sym.name, suffix) { + return sym.name, sym.address + } + } + return "", 0 +} + +// assertBPFSymbolFound polls the symbolizer until a BPF symbol matching progName +// appears, then verifies the full symbolization path (address -> symbol). +func assertBPFSymbolFound(t *testing.T, s *Symbolizer, progName string) (string, libpf.Address) { + t.Helper() + + var fullName string + var progAddr libpf.Address + require.Eventually(t, func() bool { + fullName, progAddr = findBPFSymbol(s.bpf, progName) + return fullName != "" + }, eventuallyWaitFor, eventuallyTick, + "BPF program with suffix %q not found by symbolizer", "_"+progName) + + t.Logf("Found BPF program %q at address 0x%x", fullName, progAddr) + + funcName, offset, ok := s.LookupBPFSymbol(progAddr) + require.True(t, ok, "LookupBPFSymbol failed for address 0x%x", progAddr) + assert.Equal(t, fullName, funcName) + assert.Equal(t, uint(0), offset) + + funcName, offset, ok = s.LookupBPFSymbol(progAddr + 1) + require.True(t, ok, "LookupBPFSymbol failed for address 0x%x", progAddr+1) + assert.Equal(t, fullName, funcName) + assert.Equal(t, uint(1), offset) + + return fullName, progAddr +} + +// assertBPFSymbolRemoved polls the symbolizer until the BPF symbol matching +// progName disappears. +func assertBPFSymbolRemoved(t *testing.T, s *Symbolizer, progName string) { + t.Helper() + + require.Eventually(t, func() bool { + name, _ := findBPFSymbol(s.bpf, progName) + return name == "" + }, eventuallyWaitFor, eventuallyTick, + "BPF program with suffix %q not removed from symbolizer", "_"+progName) + + t.Logf("BPF program with suffix %q successfully removed from symbolizer", "_"+progName) +} + +// TestBPFSymbolizerDynamic verifies that programs loaded after the monitor +// starts are discovered via PERF_RECORD_KSYMBOL events and that unloading +// them removes the symbols. +func TestBPFSymbolizerDynamic(t *testing.T) { + restoreRlimit, err := rlimit.MaximizeMemlock() + require.NoError(t, err) + defer restoreRlimit() + + s, err := NewSymbolizer() + require.NoError(t, err) + + err = s.bpf.startMonitor(t.Context(), linearCPUs()) + require.NoError(t, err) + defer s.bpf.Close() + + // The program hasn't been loaded yet, so the symbolizer must not know about it. + name, _ := findBPFSymbol(s.bpf, dynamicProgName) + require.Empty(t, name, "BPF program %q found before loading", dynamicProgName) + + prog := loadSocketFilter(t, dynamicProgName) + + fullName, _ := assertBPFSymbolFound(t, s, dynamicProgName) + + prog.Close() + assertBPFSymbolRemoved(t, s, dynamicProgName) + + t.Logf("Dynamic test passed: %q added and removed", fullName) +} + +// TestBPFSymbolizerPreexisting verifies that programs loaded before the +// monitor starts are discovered via the initial /proc/kallsyms parse. +func TestBPFSymbolizerPreexisting(t *testing.T) { + restoreRlimit, err := rlimit.MaximizeMemlock() + require.NoError(t, err) + defer restoreRlimit() + + // Load the program before starting the monitor. + prog := loadSocketFilter(t, preexistingProgName) + + s, err := NewSymbolizer() + require.NoError(t, err) + + err = s.bpf.startMonitor(t.Context(), linearCPUs()) + require.NoError(t, err) + defer s.bpf.Close() + + // The program was loaded before the monitor started, so it must be + // discovered from /proc/kallsyms during the initial load. + fullName, _ := assertBPFSymbolFound(t, s, preexistingProgName) + t.Logf("Preexisting program %q found from initial kallsyms load", fullName) + + // Close the program and verify the symbol is removed via perf event. + prog.Close() + assertBPFSymbolRemoved(t, s, preexistingProgName) + t.Logf("Preexisting program %q successfully removed", fullName) +} diff --git a/kallsyms/kallsyms.go b/kallsyms/kallsyms.go index efcc5e3be..c9e46cd65 100644 --- a/kallsyms/kallsyms.go +++ b/kallsyms/kallsyms.go @@ -38,6 +38,9 @@ const Kernel = "vmlinux" // from the kernel kallsyms file. const pointerBits = int(unsafe.Sizeof(libpf.Address(0)) * 8) +// maxAddr is the max address value. +const maxAddr = uint64(1< maxAddr { + return fmt.Errorf("address exceeds pointer size: %x > %x", address, maxAddr) + } if address != 0 { noSymbols = false } @@ -409,13 +417,11 @@ func (s *Symbolizer) updateSymbolsFrom(r io.Reader) error { symbols: syms[0:0], names: names[0:0], } - if moduleName != "bpf" { - oldMod, _ = getModuleByAddress(modules, libpf.Address(address)) - if oldMod != nil && !oldMod.stub && oldMod.Name() == moduleName { - oldMtime = oldMod.mtime - } else { - oldMod = nil - } + oldMod, _ = getModuleByAddress(modules, libpf.Address(address)) + if oldMod != nil && !oldMod.stub && oldMod.Name() == moduleName { + oldMtime = oldMod.mtime + } else { + oldMod = nil } if loadModuleMetadata(&newMod, moduleName, oldMtime) { // Module metadata was updated. Parse this module symbols. @@ -458,6 +464,9 @@ func (s *Symbolizer) updateSymbolsFrom(r io.Reader) error { } } } + if err := scanner.Err(); err != nil { + return fmt.Errorf("error scanning /proc/kallsyms: %w", err) + } if mod != nil { mod.finish() } @@ -486,7 +495,6 @@ func (s *Symbolizer) loadKallsyms() error { var nonsyfsModules = libpf.Set[string]{ Kernel: libpf.Void{}, - "bpf": libpf.Void{}, } // loadModules will reload module metadata. @@ -575,12 +583,6 @@ func (s *Symbolizer) reloadWorker(ctx context.Context, kobjectClient *kobject.Cl log.Warnf("Failed to reload kernel modules metadata: %v", err) nextModulesReload = time.After(10 * time.Second) } - case <-s.reloadSymbols: - // Just trigger reloading of symbols with small delay to batch - // potentially multiple module loads. - if nextKallsymsReload == noTimeout { - nextKallsymsReload = time.After(100 * time.Millisecond) - } case <-nextKallsymsReload: if err := s.loadKallsyms(); err == nil { log.Debugf("Kernel symbols reloaded") @@ -615,26 +617,28 @@ func (s *Symbolizer) pollKobjectClient(_ context.Context, kobjectClient *kobject } } -// Reload will trigger asynchronous update of modules and symbols. -func (s *Symbolizer) StartMonitor(ctx context.Context) error { +// Close frees resources associated with the Symbolizer. +func (s *Symbolizer) Close() { + s.bpf.Close() +} + +// StartMonitor starts the update monitoring for kallsyms. +func (s *Symbolizer) StartMonitor(ctx context.Context, onlineCPUs []int) error { kobjectClient, err := kobject.New() if err != nil { return fmt.Errorf("failed to create kobject netlink socket: %v", err) } + err = s.bpf.startMonitor(ctx, onlineCPUs) + if err != nil { + s.bpf.Close() + _ = kobjectClient.Close() + return err + } go s.reloadWorker(ctx, kobjectClient) go s.pollKobjectClient(ctx, kobjectClient) return nil } -// Reload triggers a non-blocking reload and update of Symbolizer -// with the recent information of /proc/kallsyms. -func (s *Symbolizer) Reload() { - select { - case s.reloadSymbols <- libpf.Void{}: - default: - } -} - // getModuleByAddress is a helper to find a Module from the sorted 'modules' // slice matching the address 'pc'. func getModuleByAddress(modules []Module, pc libpf.Address) (*Module, error) { @@ -656,7 +660,7 @@ func (s *Symbolizer) GetModuleByAddress(pc libpf.Address) (*Module, error) { return getModuleByAddress(s.modules.Load().([]Module), pc) } -// GetModuleByAddress finds the Module containing the module 'module'. +// GetModuleByName finds the Module with the given name. func (s *Symbolizer) GetModuleByName(module string) (*Module, error) { modules := s.modules.Load().([]Module) for i := range modules { @@ -667,3 +671,12 @@ func (s *Symbolizer) GetModuleByName(module string) (*Module, error) { } return nil, ErrNoModule } + +// LookupBPFSymbol resolves addr to a BPF program symbol name and offset. +// Returns ("", 0, false) if no BPF program covers addr. +func (s *Symbolizer) LookupBPFSymbol(addr libpf.Address) (string, uint, bool) { + if s.bpf == nil { + return "", 0, false + } + return s.bpf.LookupSymbol(addr) +} diff --git a/kallsyms/kallsyms_test.go b/kallsyms/kallsyms_test.go index c69471d50..dd8bed983 100644 --- a/kallsyms/kallsyms_test.go +++ b/kallsyms/kallsyms_test.go @@ -4,12 +4,16 @@ package kallsyms import ( + "cmp" "io" + "slices" "strings" "testing" "go.opentelemetry.io/ebpf-profiler/libpf" + "golang.org/x/sys/unix" + "github.com/elastic/go-perf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -105,6 +109,156 @@ ffffffffc13fcb20 t init_xfs_fs [xfs]`)) assertSymbol(t, s, 0xffffffffc13cc610+1, "xfs", "perf_trace_xfs_attr_list_class", 1) } +// setBPFSymbols stores the given symbols in the bpfSymbolizer as a sorted +// bpfSymbolTable. This replaces the production loadBPFPrograms for tests. +func setBPFSymbols(s *bpfSymbolizer, symbols []bpfSymbol) { + sorted := make([]bpfSymbol, len(symbols)) + copy(sorted, symbols) + slices.SortFunc(sorted, func(a, b bpfSymbol) int { + return cmp.Compare(a.address, b.address) + }) + s.table.Store(&bpfSymbolTable{symbols: sorted}) +} + +// assertBPFSymbol checks that the BPF symbolizer resolves addr to the expected +// function name and offset. +func assertBPFSymbol(t *testing.T, s *Symbolizer, addr libpf.Address, eFuncName string, eOffset uint) { + t.Helper() + funcName, off, ok := s.LookupBPFSymbol(addr) + if assert.True(t, ok, "expected BPF symbol at 0x%x", addr) { + assert.Equal(t, eFuncName, funcName) + assert.Equal(t, eOffset, off) + } +} + +// assertNoBPFSymbol checks that the BPF symbolizer does not resolve addr. +func assertNoBPFSymbol(t *testing.T, s *Symbolizer, addr libpf.Address) { + t.Helper() + _, _, ok := s.LookupBPFSymbol(addr) + assert.False(t, ok, "expected no BPF symbol at 0x%x", addr) +} + +func TestBPFUpdates(t *testing.T) { + s := &Symbolizer{ + bpf: &bpfSymbolizer{}, + } + + bpfSymbols := []bpfSymbol{ + {address: 0xffffffc080f26228, size: 512, name: "bpf_prog_00354c172d366337_sd_devices"}, + {address: 0xffffffc080f26430, size: 512, name: "bpf_prog_772db7720b2728e9_sd_fw_egress"}, + {address: 0xffffffc080f264d8, size: 512, name: "bpf_prog_772db7720b2728e9_sd_fw_ingress"}, + {address: 0xffffffc080f28490, size: 512, name: "bpf_prog_56551fa66be1356a_sd_devices"}, + {address: 0xffffffc080f2867c, size: 512, name: "bpf_prog_772db7720b2728e9_sd_fw_egress"}, + {address: 0xffffffc080f2871c, size: 512, name: "bpf_prog_772db7720b2728e9_sd_fw_ingress"}, + {address: 0xffffffc080f2da64, size: 512, name: "bpf_prog_00354c172d366337_sd_devices"}, + {address: 0xffffffc080f304a0, size: 512, name: "bpf_prog_5be112cdf63b0d8c_sysctl_monitor"}, + {address: 0xffffffc080f3089c, size: 512, name: "bpf_prog_292e0637857c1257_cut_last"}, + {address: 0xffffffc080f3096c, size: 512, name: "bpf_prog_a97c143260cd9940_sd_devices"}, + {address: 0xffffffc080f32f4c, size: 512, name: "bpf_prog_79c5319176ee7ce5_sd_devices"}, + {address: 0xffffffc080f331e4, size: 512, name: "bpf_prog_772db7720b2728e9_sd_fw_egress"}, + {address: 0xffffffc080f33288, size: 512, name: "bpf_prog_772db7720b2728e9_sd_fw_ingress"}, + {address: 0xffffffc080f35f1c, size: 512, name: "bpf_prog_461f9f5162fd8042_sd_devices"}, + {address: 0xffffffc080f3629c, size: 512, name: "bpf_prog_b8f4fb5f08605bc5"}, + } + + setBPFSymbols(s.bpf, bpfSymbols) + + // Adding a symbol at the end with a known size of 12288 bytes. This ensures + // that an address 10240 bytes into the symbol is covered even though that + // far exceeds a single page past the symbol start. + const lastSymAddr = libpf.Address(0xffffffc080f38288) + const lastSymSize = uint32(12288) + err := s.bpf.handleBPFUpdate(&perf.KSymbolRecord{ + Addr: uint64(lastSymAddr), + Len: lastSymSize, + Name: "bpf_prog_05cbe5ca7b74dd09_sys_enter", + }) + require.NoError(t, err) + + // exact symbol match + assertBPFSymbol(t, s, lastSymAddr, "bpf_prog_05cbe5ca7b74dd09_sys_enter", 0) + + // 10240 bytes into the last symbol must resolve correctly + assertBPFSymbol(t, s, lastSymAddr+10240, "bpf_prog_05cbe5ca7b74dd09_sys_enter", 10240) + + // address beyond the symbol's end must not resolve + assertNoBPFSymbol(t, s, lastSymAddr+libpf.Address(lastSymSize)) + + // remove the added symbol + err = s.bpf.handleBPFUpdate(&perf.KSymbolRecord{ + Addr: uint64(lastSymAddr), + Name: "bpf_prog_05cbe5ca7b74dd09_sys_enter", + Flags: unix.PERF_RECORD_KSYMBOL_FLAGS_UNREGISTER, + }) + require.NoError(t, err) + + // the address goes poof + assertNoBPFSymbol(t, s, lastSymAddr) + + // add it back + err = s.bpf.handleBPFUpdate(&perf.KSymbolRecord{ + Addr: uint64(lastSymAddr), + Len: lastSymSize, + Name: "bpf_prog_05cbe5ca7b74dd09_sys_enter", + }) + require.NoError(t, err) + + // find a pre-existing symbol by aiming slightly above its start + assertBPFSymbol(t, s, 0xffffffc080f3089e, "bpf_prog_292e0637857c1257_cut_last", 0x2) + + // remove the pre-existing symbol + err = s.bpf.handleBPFUpdate(&perf.KSymbolRecord{ + Addr: 0xffffffc080f3089c, + Name: "bpf_prog_292e0637857c1257_cut_last", + Flags: unix.PERF_RECORD_KSYMBOL_FLAGS_UNREGISTER, + }) + require.NoError(t, err) + + // the address no longer resolves (previous symbol ends before 0x3089e) + assertNoBPFSymbol(t, s, 0xffffffc080f3089e) + + // put the removed symbol back + err = s.bpf.handleBPFUpdate(&perf.KSymbolRecord{ + Addr: 0xffffffc080f3089c, + Len: 512, + Name: "bpf_prog_292e0637857c1257_cut_last", + }) + require.NoError(t, err) + + // and it's right there where we put it + assertBPFSymbol(t, s, 0xffffffc080f3089e, "bpf_prog_292e0637857c1257_cut_last", 0x2) + + // checking for lost symbols triggering full reload + err = s.bpf.handleBPFUpdate(nil) + assert.NotNil(t, err) + + // trampolines and non-bpf_prog_ symbols are silently ignored + err = s.bpf.handleBPFUpdate(&perf.KSymbolRecord{ + Addr: 0xffffffc080f26226, + Name: "bpf_trampoline_6442536467", + }) + require.NoError(t, err) + assertNoBPFSymbol(t, s, 0xffffffc080f26226) + + // a bpf_prog_ symbol added before existing ones is found correctly + err = s.bpf.handleBPFUpdate(&perf.KSymbolRecord{ + Addr: 0xffffffc080f26000, + Len: 512, + Name: "bpf_prog_earliest", + }) + require.NoError(t, err) + assertBPFSymbol(t, s, 0xffffffc080f26000, "bpf_prog_earliest", 0) + + // removing it works + err = s.bpf.handleBPFUpdate(&perf.KSymbolRecord{ + Addr: 0xffffffc080f26000, + Name: "bpf_prog_earliest", + Flags: unix.PERF_RECORD_KSYMBOL_FLAGS_UNREGISTER, + }) + require.NoError(t, err) + assertNoBPFSymbol(t, s, 0xffffffc080f26000) +} + func BenchmarkSort(b *testing.B) { r := strings.NewReader(`0000000000000000 A __per_cpu_start 0000000000001000 A cpu_debug_store diff --git a/libc/libc.go b/libc/libc.go index 25343cb6a..576cde5a6 100644 --- a/libc/libc.go +++ b/libc/libc.go @@ -66,20 +66,24 @@ func IsPotentialLibcDSO(filename string) bool { } func ExtractLibcInfo(ef *pfelf.File) (*LibcInfo, error) { - tsdinfo, err := extractTSDInfo(ef) - if err != nil { - return nil, err + info := &LibcInfo{} + + tsdinfo, tsdErr := extractTSDInfo(ef) + if tsdErr == nil { + info.TSDInfo = tsdinfo } - dtvinfo, err := extractDTVInfo(ef) - if err != nil { - return &LibcInfo{}, err + dtvinfo, dtvErr := extractDTVInfo(ef) + if dtvErr == nil { + info.DTVInfo = dtvinfo } - return &LibcInfo{ - TSDInfo: tsdinfo, - DTVInfo: dtvinfo, - }, nil + // Return an error only if both extractions failed. + if tsdErr != nil && dtvErr != nil { + return nil, fmt.Errorf("TSD: %s; DTV: %s", tsdErr, dtvErr) + } + + return info, nil } // This code analyzes the C-library provided POSIX defined function which is used diff --git a/libc/libc_aarch64.go b/libc/libc_aarch64.go index d389694d8..b97eb23e1 100644 --- a/libc/libc_aarch64.go +++ b/libc/libc_aarch64.go @@ -408,6 +408,5 @@ func extractDTVInfoARM(code []byte) (DTVInfo, error) { return DTVInfo{ Offset: dtvOffset, Multiplier: uint8(entryWidth), - Indirect: 1, }, nil } diff --git a/libc/libc_test.go b/libc/libc_test.go index 24b5b81af..4a827380a 100644 --- a/libc/libc_test.go +++ b/libc/libc_test.go @@ -4,10 +4,15 @@ package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( + "bytes" "debug/elf" + "encoding/binary" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" ) func TestExtractTSDInfo(t *testing.T) { @@ -313,7 +318,6 @@ func TestExtractDTVOffset(t *testing.T) { info: DTVInfo{ Offset: 8, Multiplier: 16, - Indirect: 0, }, }, "glibc 2.32 / Fedora 33 / x86_64": { @@ -361,7 +365,6 @@ func TestExtractDTVOffset(t *testing.T) { info: DTVInfo{ Offset: 8, Multiplier: 16, - Indirect: 0, }, }, "musl 1.2.5 / alpine 3.22.2 / x86_64": { @@ -383,7 +386,6 @@ func TestExtractDTVOffset(t *testing.T) { info: DTVInfo{ Offset: 8, Multiplier: 8, - Indirect: 1, }, }, "musl 1.1.5 / alpine 3.1 / x86_64": { @@ -411,7 +413,6 @@ func TestExtractDTVOffset(t *testing.T) { info: DTVInfo{ Offset: 8, Multiplier: 8, - Indirect: 1, }, }, "glibc 2.39 / ubuntu 24.04 / aarch64": { @@ -447,7 +448,6 @@ func TestExtractDTVOffset(t *testing.T) { info: DTVInfo{ Offset: 0, Multiplier: 16, - Indirect: 1, }, }, "glibc / Fedora 39 / aarch64": { @@ -486,7 +486,6 @@ func TestExtractDTVOffset(t *testing.T) { info: DTVInfo{ Offset: 0, Multiplier: 16, - Indirect: 1, }, }, "glibc / Fedora 33 / aarch64": { @@ -515,7 +514,6 @@ func TestExtractDTVOffset(t *testing.T) { info: DTVInfo{ Offset: 0, Multiplier: 16, - Indirect: 1, }, }, @@ -532,7 +530,6 @@ func TestExtractDTVOffset(t *testing.T) { info: DTVInfo{ Offset: -8, Multiplier: 8, - Indirect: 1, }, }, } @@ -554,6 +551,241 @@ func TestExtractDTVOffset(t *testing.T) { } } +// buildTestELF creates a minimal 64-bit ELF binary with the given dynamic symbols. +// Each symbol maps to its corresponding code byte slice. The resulting ELF has a +// SysV hash table so pfelf.File can resolve symbols via LookupSymbol/SymbolData. +func buildTestELF(t *testing.T, machine elf.Machine, symbols map[string][]byte) *pfelf.File { + t.Helper() + + // Layout: ELF header | Phdr[0] PT_LOAD | Phdr[1] PT_DYNAMIC | + // strtab | symtab | hash | dyntab | code... + // + // Everything lives in one PT_LOAD segment starting at vaddr 0. + const vaddr = uint64(0x1000) + + // Build string table: \0 then each symbol name \0-terminated + var strtab bytes.Buffer + strtab.WriteByte(0) // index 0 = empty string + nameOffsets := make(map[string]uint32) + for name := range symbols { + nameOffsets[name] = uint32(strtab.Len()) + strtab.WriteString(name) + strtab.WriteByte(0) + } + + // Build symbol table: Sym64[0] is always null, then one per symbol + numSyms := 1 + len(symbols) + symtab := make([]elf.Sym64, numSyms) + // Symbol index 0 is reserved (STN_UNDEF) + + // We'll fill in addresses after we know the code layout + symOrder := make([]string, 0, len(symbols)) + for name := range symbols { + symOrder = append(symOrder, name) + } + + // sysvHash computes the ELF SysV hash for a symbol name. + sysvHash := func(s string) uint32 { + h := uint32(0) + for _, c := range []byte(s) { + h = 16*h + uint32(c) + h ^= h >> 24 & 0xf0 + } + return h & 0xfffffff + } + + // Build SysV hash table + // nbucket = numSyms, nchain = numSyms (simple 1:1 mapping) + nbucket := uint32(numSyms) + nchain := uint32(numSyms) + + // Now compute sizes for layout + ehdrSize := int(binary.Size(elf.Header64{})) + phdrSize := int(binary.Size(elf.Prog64{})) + numPhdrs := 2 + + strtabOff := ehdrSize + phdrSize*numPhdrs + symtabOff := strtabOff + strtab.Len() + hashOff := symtabOff + numSyms*int(binary.Size(elf.Sym64{})) + hashSize := int(4 + 4 + 4*int(nbucket) + 4*int(nchain)) // nbucket, nchain, buckets, chains + dynOff := hashOff + hashSize + dynSize := int(4 * binary.Size(elf.Dyn64{})) // STRTAB, SYMTAB, HASH, NULL + codeOff := dynOff + dynSize + + // Place code for each symbol + codeOffsets := make(map[string]int) + offset := codeOff + for _, name := range symOrder { + codeOffsets[name] = offset + offset += len(symbols[name]) + } + totalSize := offset + + // Fill in symbol table entries + for i, name := range symOrder { + idx := i + 1 // skip null symbol at index 0 + symtab[idx] = elf.Sym64{ + Name: nameOffsets[name], + Info: byte(elf.STB_GLOBAL)<<4 | byte(elf.STT_FUNC), + Other: byte(elf.STV_DEFAULT), + Shndx: 1, // non-zero = defined + Value: vaddr + uint64(codeOffsets[name]), + Size: uint64(len(symbols[name])), + } + } + + // Build SysV hash: simple bucket[hash % nbucket] = sym_index, chain = 0 + hashBuf := make([]byte, hashSize) + binary.LittleEndian.PutUint32(hashBuf[0:], nbucket) + binary.LittleEndian.PutUint32(hashBuf[4:], nchain) + bucketsStart := 8 + chainsStart := bucketsStart + 4*int(nbucket) + + // Initialize all buckets and chains to 0 (STN_UNDEF) + for i, name := range symOrder { + symIdx := uint32(i + 1) + h := sysvHash(name) + bucket := h % nbucket + bucketOff := bucketsStart + 4*int(bucket) + existing := binary.LittleEndian.Uint32(hashBuf[bucketOff:]) + if existing == 0 { + binary.LittleEndian.PutUint32(hashBuf[bucketOff:], symIdx) + } else { + // Chain from existing + cur := existing + for { + chainOff := chainsStart + 4*int(cur) + next := binary.LittleEndian.Uint32(hashBuf[chainOff:]) + if next == 0 { + binary.LittleEndian.PutUint32(hashBuf[chainOff:], symIdx) + break + } + cur = next + } + } + } + + // Build dynamic table + dynEntries := []elf.Dyn64{ + {Tag: int64(elf.DT_STRTAB), Val: vaddr + uint64(strtabOff)}, + {Tag: int64(elf.DT_SYMTAB), Val: vaddr + uint64(symtabOff)}, + {Tag: int64(elf.DT_HASH), Val: vaddr + uint64(hashOff)}, + {Tag: int64(elf.DT_NULL), Val: 0}, + } + + // Assemble the ELF + buf := make([]byte, totalSize) + + // ELF header + hdr := elf.Header64{ + Ident: [16]byte{0x7f, 'E', 'L', 'F', byte(elf.ELFCLASS64), byte(elf.ELFDATA2LSB), byte(elf.EV_CURRENT)}, + Type: uint16(elf.ET_DYN), + Machine: uint16(machine), + Version: uint32(elf.EV_CURRENT), + Entry: vaddr + uint64(codeOff), + Phoff: uint64(ehdrSize), + Ehsize: uint16(ehdrSize), + Phentsize: uint16(phdrSize), + Phnum: uint16(numPhdrs), + } + binary.Encode(buf[0:], binary.LittleEndian, &hdr) + + // Program headers + phLoad := elf.Prog64{ + Type: uint32(elf.PT_LOAD), + Flags: uint32(elf.PF_R | elf.PF_X), + Off: 0, + Vaddr: vaddr, + Paddr: vaddr, + Filesz: uint64(totalSize), + Memsz: uint64(totalSize), + Align: 0x1000, + } + binary.Encode(buf[ehdrSize:], binary.LittleEndian, &phLoad) + + phDyn := elf.Prog64{ + Type: uint32(elf.PT_DYNAMIC), + Flags: uint32(elf.PF_R), + Off: uint64(dynOff), + Vaddr: vaddr + uint64(dynOff), + Paddr: vaddr + uint64(dynOff), + Filesz: uint64(dynSize), + Memsz: uint64(dynSize), + Align: 8, + } + binary.Encode(buf[ehdrSize+phdrSize:], binary.LittleEndian, &phDyn) + + // String table + copy(buf[strtabOff:], strtab.Bytes()) + + // Symbol table + for i, sym := range symtab { + binary.Encode(buf[symtabOff+i*int(binary.Size(elf.Sym64{})):], binary.LittleEndian, &sym) + } + + // Hash table + copy(buf[hashOff:], hashBuf) + + // Dynamic table + for i, dyn := range dynEntries { + binary.Encode(buf[dynOff+i*int(binary.Size(elf.Dyn64{})):], binary.LittleEndian, &dyn) + } + + // Code sections + for _, name := range symOrder { + copy(buf[codeOffsets[name]:], symbols[name]) + } + + ef, err := pfelf.NewFile(bytes.NewReader(buf), 0, false) + require.NoError(t, err) + return ef +} + +// TestExtractLibcInfoIndependence verifies that TSD and DTV extraction are +// independent: failure to extract one should not prevent extraction of the other. +// This is a regression test for a bug where ExtractLibcInfo would bail out +// entirely if extractTSDInfo failed, even when __tls_get_addr was available +// (e.g., in ld-linux.so which exports __tls_get_addr but not pthread_getspecific). +func TestExtractLibcInfoIndependence(t *testing.T) { + // glibc 2.36 / debian 12 / x86_64 __tls_get_addr machine code + tlsGetAddrCode := []byte{ + 0x64, 0x48, 0x8b, 0x14, 0x25, 0x08, 0x00, 0x00, 0x00, + 0x48, 0x8b, 0x05, 0x48, 0xfc, 0x01, 0x00, + 0x48, 0x39, 0x02, + 0x75, 0x16, + 0x48, 0x8b, 0x07, + 0x48, 0xc1, 0xe0, 0x04, + 0x48, 0x8b, 0x04, 0x02, + 0x48, 0x83, 0xf8, 0xff, + 0x74, 0x05, + 0x48, 0x03, 0x47, 0x08, + 0xc3, + } + + // Build a minimal ELF with ONLY __tls_get_addr (no pthread_getspecific). + // This simulates ld-linux.so which exports __tls_get_addr but not the + // pthread_getspecific symbol. + ef := buildTestELF(t, elf.EM_X86_64, map[string][]byte{ + "__tls_get_addr": tlsGetAddrCode, + }) + + // Call ExtractLibcInfo — previously this would return (nil, err) because + // extractTSDInfo failed first and short-circuited the DTV extraction. + info, err := ExtractLibcInfo(ef) + + // Should succeed (no error) because DTV extraction works even though TSD fails. + assert.NoError(t, err) + require.NotNil(t, info, "ExtractLibcInfo should return non-nil LibcInfo") + + // TSD should be empty (no pthread_getspecific symbol) + assert.False(t, info.HasTSDInfo(), "should not have TSD info") + + // DTV should be populated from __tls_get_addr + assert.True(t, info.HasDTVInfo(), "should have DTV info from __tls_get_addr") + assert.Equal(t, int16(8), info.DTVInfo.Offset) + assert.Equal(t, uint8(16), info.DTVInfo.Multiplier) +} + func TestLibcInfoIsEqual(t *testing.T) { testCases := map[string]struct { left LibcInfo @@ -575,7 +807,6 @@ func TestLibcInfoIsEqual(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, right: LibcInfo{ @@ -587,7 +818,6 @@ func TestLibcInfoIsEqual(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, expectEqual: true, @@ -598,7 +828,6 @@ func TestLibcInfoIsEqual(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, right: LibcInfo{ @@ -652,7 +881,6 @@ func TestLibcInfoMerge(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, expected: LibcInfo{ @@ -660,7 +888,6 @@ func TestLibcInfoMerge(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, }, @@ -670,7 +897,6 @@ func TestLibcInfoMerge(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, right: LibcInfo{ @@ -690,7 +916,6 @@ func TestLibcInfoMerge(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, }, @@ -708,7 +933,6 @@ func TestLibcInfoMerge(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, expected: LibcInfo{ @@ -720,7 +944,6 @@ func TestLibcInfoMerge(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, }, @@ -737,7 +960,6 @@ func TestLibcInfoMerge(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, right: LibcInfo{ @@ -749,7 +971,6 @@ func TestLibcInfoMerge(t *testing.T) { DTVInfo{ Offset: 8, Multiplier: 16, - Indirect: 1, }, }, expected: LibcInfo{ @@ -761,7 +982,6 @@ func TestLibcInfoMerge(t *testing.T) { DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 0, }, }, }, diff --git a/libc/libc_x86.go b/libc/libc_x86.go index 7df5cb792..8778958a0 100644 --- a/libc/libc_x86.go +++ b/libc/libc_x86.go @@ -107,7 +107,9 @@ func extractDTVInfoX86(code []byte) (DTVInfo, error) { entryWidth = e.NewImmediateCapture("entryWidth") ) - // Pattern 1: glibc - Direct DTV access + // Pattern 1: glibc - DTV pointer loaded from FS:offset, then indexed + // FS:offset is a memory load that retrieves the DTV pointer, so this is + // indirect access (two dereferences: load DTV ptr, then index into DTV). expected := e.Add( e.Mem8( e.Add( @@ -122,7 +124,6 @@ func extractDTVInfoX86(code []byte) (DTVInfo, error) { return DTVInfo{ Offset: int16(dtvOffset.CapturedValue() & 0xFFFF), Multiplier: uint8(entryWidth.CapturedValue()), - Indirect: 0, }, nil } @@ -145,7 +146,6 @@ func extractDTVInfoX86(code []byte) (DTVInfo, error) { return DTVInfo{ Offset: int16(dtvOffset.CapturedValue() & 0xFFFF), Multiplier: uint8(entryWidth.CapturedValue()), - Indirect: 1, }, nil } @@ -164,7 +164,6 @@ func extractDTVInfoX86(code []byte) (DTVInfo, error) { return DTVInfo{ Offset: int16(dtvOffset.CapturedValue() & 0xFFFF), Multiplier: uint8(entryWidth.CapturedValue()), - Indirect: 1, }, nil } @@ -184,7 +183,6 @@ func extractDTVInfoX86(code []byte) (DTVInfo, error) { return DTVInfo{ Offset: int16(dtvOffset.CapturedValue() & 0xFFFF), Multiplier: uint8(entryWidth.CapturedValue()), - Indirect: 1, }, nil } diff --git a/libpf/apm.go b/libpf/apm.go index 903316b05..530c6ab3a 100644 --- a/libpf/apm.go +++ b/libpf/apm.go @@ -8,3 +8,4 @@ type APMTraceID [16]byte type APMTransactionID = APMSpanID var InvalidAPMSpanID = APMSpanID{0, 0, 0, 0, 0, 0, 0, 0} +var InvalidAPMTraceID = APMTraceID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} diff --git a/libpf/frametype.go b/libpf/frametype.go index 53337a22f..af47bf6cc 100644 --- a/libpf/frametype.go +++ b/libpf/frametype.go @@ -53,6 +53,8 @@ const ( GoFrame FrameType = support.FrameMarkerGo // BEAMFrame identifies the BEAM interpreter frames. BEAMFrame FrameType = support.FrameMarkerBEAM + // LuaJITFrame identifies the LuaJIT interpreter frames. + LuaJITFrame FrameType = support.FrameMarkerLuaJIT ) const ( diff --git a/libpf/frametype_test.go b/libpf/frametype_test.go index 730e56aa6..ef27447b1 100644 --- a/libpf/frametype_test.go +++ b/libpf/frametype_test.go @@ -13,7 +13,7 @@ func TestFrameTypeFromString(t *testing.T) { // Simple check whether all FrameType values can be converted to string and back. for _, ft := range []FrameType{ UnknownFrame, PHPFrame, PythonFrame, NativeFrame, KernelFrame, HotSpotFrame, RubyFrame, - PerlFrame, V8Frame, DotnetFrame} { + PerlFrame, V8Frame, DotnetFrame, LuaJITFrame} { t.Run(ft.String(), func(t *testing.T) { name := ft.String() result := FrameTypeFromString(name) diff --git a/libpf/interpretertype.go b/libpf/interpretertype.go index ff581e117..7ee09a75f 100644 --- a/libpf/interpretertype.go +++ b/libpf/interpretertype.go @@ -31,6 +31,8 @@ const ( V8 InterpreterType = support.FrameMarkerV8 // Dotnet identifies the Dotnet interpreter. Dotnet InterpreterType = support.FrameMarkerDotnet + // LuaJIT identifies the LuaJIT interpreter. + LuaJIT InterpreterType = support.FrameMarkerLuaJIT // Go identifies Go code. Go InterpreterType = support.FrameMarkerGo // BEAM identifies the BEAM interpreter. @@ -73,6 +75,7 @@ var interpreterTypeToString = map[InterpreterType]string{ Dotnet: "dotnet", BEAM: "beam", APMInt: "apm-integration", + LuaJIT: "luajit", Go: "go", GoLabels: "go-labels", } diff --git a/libpf/pfelf/file.go b/libpf/pfelf/file.go index 5791931c0..cc82c2a34 100644 --- a/libpf/pfelf/file.go +++ b/libpf/pfelf/file.go @@ -27,6 +27,7 @@ import ( "fmt" "hash/crc32" "io" + "os" "path/filepath" "runtime" "runtime/debug" @@ -67,6 +68,9 @@ type File struct { // elfReader is the ReadAt implementation used for this File elfReader io.ReaderAt + // mmapReader is the mmap reader for this File if available + mmapReader *mmap.ReaderAt + // ehFrame is a pointer to the PT_GNU_EH_FRAME segment of the ELF ehFrame *Prog @@ -170,7 +174,7 @@ type Section struct { // Open opens the named file using os.Open and prepares it for use as an ELF binary. func Open(name string) (*File, error) { - f, err := mmap.Open(name) + f, err := os.Open(name) if err != nil { return nil, err } @@ -185,6 +189,9 @@ func Open(name string) (*File, error) { // Close closes the File. func (f *File) Close() (err error) { + if f.mmapReader != nil { + f.mmapReader.Close() + } if f.closer != nil { err = f.closer.Close() f.closer = nil @@ -235,6 +242,11 @@ func newFile(r io.ReaderAt, closer io.Closer, return nil, err } + if osFile, ok := r.(*os.File); ok { + // Attempt to mmap the file if possible + f.mmapReader, _ = mmap.OpenFile(osFile) + } + f.Progs = make([]Prog, hdr.Phnum) virtualBase := ^uint64(0) numROData := 0 @@ -251,7 +263,7 @@ func newFile(r io.ReaderAt, closer io.Closer, Memsz: ph.Memsz, Align: ph.Align, } - p.elfReader = r + p.elfReader = f.getReader() if p.Type == elf.PT_LOAD { if p.Vaddr < virtualBase { @@ -362,12 +374,21 @@ func (_ NoMmapCloser) Close() error { // keep slices returned by Section.Data() and Prog.Data() after File has been // GCd. The returned Close() will release the reference on data. func (f *File) Take() io.Closer { - if mapping, ok := f.elfReader.(*mmap.ReaderAt); ok { - return mapping.Take() + if f.mmapReader != nil { + return f.mmapReader.Take() } return NoMmapCloser{} } +// getReader returns the mmap reader if available, or otherwise the underlying +// reader (typically os.File). +func (f *File) getReader() io.ReaderAt { + if f.mmapReader != nil { + return f.mmapReader + } + return f.elfReader +} + // LoadSections loads the ELF file sections func (f *File) LoadSections() error { if f.InsideCore { @@ -412,7 +433,7 @@ func (f *File) LoadSections() error { Entsize: sh.Entsize, FileSize: sh.Size, } - s.elfReader = f.elfReader + s.elfReader = f.getReader() } // Load the section name string table @@ -487,34 +508,19 @@ func (f *File) VirtualMemory(addr int64, sz, maxSize int) ([]byte, error) { return nil, fmt.Errorf("no matching segment for 0x%x", uint64(addr)) } -// SymbolData returns the data associated with given dynamic symbol. -// The backing mmapped data is returned if possible, otherwise a maximum of -// maxCopy bytes of the symbol data will read to newly allocated buffer. -func (f *File) SymbolData(name libpf.SymbolName, maxCopy int) (*libpf.Symbol, []byte, error) { +// SymbolData reads and returns the data associated with given dynamic symbol. +// The maximum data read is capped to maxSize. +func (f *File) SymbolData(name libpf.SymbolName, maxSize int) (*libpf.Symbol, []byte, error) { sym, err := f.LookupSymbol(name) if err != nil { return nil, nil, err } - symSize := int(sym.Size) - if symSize > maxCopy { - // Truncate read size if not memory mapped data. - if _, ok := f.elfReader.(*mmap.ReaderAt); !ok { - symSize = maxCopy - } - } - data, err := f.VirtualMemory(int64(sym.Address), symSize, maxCopy) - return sym, data, err -} - -// ReadVirtualMemory reads bytes from given virtual address -func (f *File) ReadVirtualMemory(p []byte, addr int64) (int, error) { - if len(p) == 0 { - return 0, nil - } - if ph := f.findVirtualAddressProg(uint64(addr)); ph != nil { - return ph.ReadAt(p, addr-int64(ph.Vaddr)) + data := make([]byte, min(int(sym.Size), maxSize)) + _, err = f.ReadAt(data, int64(sym.Address)) + if err != nil { + return nil, nil, err } - return 0, fmt.Errorf("no matching segment for 0x%x", uint64(addr)) + return sym, data, nil } // EHFrame constructs a Program header with the EH Frame sections @@ -546,7 +552,7 @@ func (f *File) EHFrame() (*Prog, error) { Memsz: ph.Memsz - offs, Align: ph.Align, }, - elfReader: f.elfReader, + elfReader: f.getReader(), }, nil } return nil, errors.New("no PT_LOAD segment for PT_GNU_EH_FRAME found") @@ -597,7 +603,7 @@ func (f *File) GoVersion() (string, error) { if !f.IsGolang() { return "", nil } - bi, err := buildinfo.Read(f.elfReader) + bi, err := buildinfo.Read(f.getReader()) if err != nil { return "", err } @@ -635,10 +641,65 @@ func (f *File) DebuglinkFileName(elfFilePath string, elfOpener ELFOpener) string type ElfReloc *elf.Rela64 -// VisitTLSDescriptors visits all TLS relocations and provides the relocation -// for the TLS symbol, as well as a best-effort string for the symbol's name -// it continues until the visitor returns false +// RelocType represents an architecture-independent relocation type. +// Multiple values can be combined with bitwise OR to match several types. +type RelocType uint32 + +const ( + // RelTLSDESC matches TLSDESC relocations (R_AARCH64_TLSDESC, R_X86_64_TLSDESC). + RelTLSDESC RelocType = 1 << iota + // RelDTPMOD64 matches DTPMOD64 relocations (R_AARCH64_TLS_DTPMOD64, R_X86_64_DTPMOD64). + RelDTPMOD64 +) + +// classifyRelocAarch64 returns the RelocType for an AARCH64 relocation. +func classifyRelocAarch64(rela ElfReloc) RelocType { + switch elf.R_AARCH64(rela.Info & 0xffff) { + case elf.R_AARCH64_TLSDESC: + return RelTLSDESC + case elf.R_AARCH64_TLS_DTPMOD64: + return RelDTPMOD64 + default: + return 0 + } +} + +// classifyRelocX86_64 returns the RelocType for an X86_64 relocation. +func classifyRelocX86_64(rela ElfReloc) RelocType { + switch elf.R_X86_64(rela.Info & 0xffff) { + case elf.R_X86_64_TLSDESC: + return RelTLSDESC + case elf.R_X86_64_DTPMOD64: + return RelDTPMOD64 + default: + return 0 + } +} + +// VisitTLSRelocations visits all TLSDESC relocations and provides the relocation +// for the TLS symbol, as well as a best-effort string for the symbol's name. +// It continues until the visitor returns false. func (f *File) VisitTLSRelocations(visitor func(ElfReloc, string) bool) error { + return f.VisitRelocations(visitor, RelTLSDESC) +} + +// VisitRelocations visits all relocations whose type matches the relTypes +// bitmask and provides the relocation and symbol name to the visitor. The +// visitor can return false to stop iteration. +func (f *File) VisitRelocations(visitor func(ElfReloc, string) bool, + relTypes RelocType) error { + var classify func(ElfReloc) RelocType + switch f.Machine { + case elf.EM_AARCH64: + classify = classifyRelocAarch64 + case elf.EM_X86_64: + classify = classifyRelocX86_64 + default: + return nil + } + filterFunc := func(rela ElfReloc) bool { + return classify(rela)&relTypes != 0 + } var err error if err = f.LoadSections(); err != nil { return err @@ -648,7 +709,7 @@ func (f *File) VisitTLSRelocations(visitor func(ElfReloc, string) bool) error { section := &f.Sections[i] // NOTE: SHT_REL is not relevant for the archs that we care about if section.Type == elf.SHT_RELA { - cont, err := f.visitTLSDescriptorsForSection(visitor, section) + cont, err := f.visitRelocationsForSection(visitor, filterFunc, section) if err != nil { return err } @@ -661,7 +722,8 @@ func (f *File) VisitTLSRelocations(visitor func(ElfReloc, string) bool) error { return nil } -func (f *File) visitTLSDescriptorsForSection(visitor func(ElfReloc, string) bool, +func (f *File) visitRelocationsForSection(visitor func(ElfReloc, string) bool, + checkRelocation func(ElfReloc) bool, relaSection *Section, ) (bool, error) { if relaSection.Link > uint32(len(f.Sections)) { @@ -704,9 +766,7 @@ func (f *File) visitTLSDescriptorsForSection(visitor func(ElfReloc, string) bool for i := 0; i < len(relaData); i += relaSz { rela := (*elf.Rela64)(unsafe.Pointer(&relaData[i])) - ty := rela.Info & 0xffff - if !(f.Machine == elf.EM_AARCH64 && elf.R_AARCH64(ty) == elf.R_AARCH64_TLSDESC) && - !(f.Machine == elf.EM_X86_64 && elf.R_X86_64(ty) == elf.R_X86_64_TLSDESC) { + if !checkRelocation(rela) { continue } @@ -833,11 +893,6 @@ func (ph *Prog) ReadAt(p []byte, off int64) (n int, err error) { return n, nil } -// Open returns a new ReadSeeker reading the ELF program body. -func (ph *Prog) Open() io.ReadSeeker { - return io.NewSectionReader(ph, 0, 1<<63-1) -} - // Data loads the whole program header referenced data, and returns it as slice. func (ph *Prog) Data(maxSize uint) ([]byte, error) { if mapping, ok := ph.elfReader.(*mmap.ReaderAt); ok { @@ -900,8 +955,8 @@ func (sh *Section) Data(maxSize uint) ([]byte, error) { // SetDontNeed sets the flag MADV_DONTNEED on the mmapped data. func (f *File) SetDontNeed() { - if mapping, ok := f.elfReader.(*mmap.ReaderAt); ok { - if err := mapping.SetMadvDontNeed(); err != nil { + if f.mmapReader != nil { + if err := f.mmapReader.SetMadvDontNeed(); err != nil { log.Errorf("Failed to set MADV_DONTNEED: %v", err) } } @@ -909,7 +964,13 @@ func (f *File) SetDontNeed() { // ReadAt reads bytes from given virtual address func (f *File) ReadAt(p []byte, addr int64) (int, error) { - return f.ReadVirtualMemory(p, addr) + if len(p) == 0 { + return 0, nil + } + if ph := f.findVirtualAddressProg(uint64(addr)); ph != nil { + return ph.ReadAt(p, addr-int64(ph.Vaddr)) + } + return 0, fmt.Errorf("no matching segment for 0x%x", uint64(addr)) } // GetRemoteMemory returns RemoteMemory interface for the core dump @@ -926,7 +987,7 @@ func (f *File) readAndMatchSymbol(n uint32, name libpf.SymbolName) (libpf.Symbol // Read symbol descriptor and expected name symSz := int64(unsafe.Sizeof(sym)) - if _, err := f.ReadVirtualMemory(pfunsafe.FromPointer(&sym), + if _, err := f.ReadAt(pfunsafe.FromPointer(&sym), f.symbolsAddr+int64(n)*symSz); err != nil { return libpf.Symbol{}, false } @@ -974,7 +1035,7 @@ func (f *File) LookupSymbol(symbol libpf.SymbolName) (*libpf.Symbol, error) { // blog link (on top of this file) for details how this works. hdr := &f.gnuHash.header if hdr.numBuckets == 0 { - if _, err := f.ReadVirtualMemory(pfunsafe.FromPointer(hdr), f.gnuHash.addr); err != nil { + if _, err := f.ReadAt(pfunsafe.FromPointer(hdr), f.gnuHash.addr); err != nil { return nil, err } if hdr.numBuckets == 0 || hdr.bloomSize == 0 { @@ -988,7 +1049,7 @@ func (f *File) LookupSymbol(symbol libpf.SymbolName) (*libpf.Symbol, error) { var bloom uint h := calcGNUHash(symbol) offs := f.gnuHash.addr + int64(unsafe.Sizeof(gnuHashHeader{})) - if _, err := f.ReadVirtualMemory(pfunsafe.FromPointer(&bloom), offs+ + if _, err := f.ReadAt(pfunsafe.FromPointer(&bloom), offs+ ptrSize*int64((h/ptrSizeBits)%hdr.bloomSize)); err != nil { return nil, err } @@ -1001,7 +1062,7 @@ func (f *File) LookupSymbol(symbol libpf.SymbolName) (*libpf.Symbol, error) { // Read the initial symbol index to start looking from offs += int64(hdr.bloomSize) * int64(unsafe.Sizeof(bloom)) var i uint32 - if _, err := f.ReadVirtualMemory(pfunsafe.FromPointer(&i), + if _, err := f.ReadAt(pfunsafe.FromPointer(&i), offs+4*int64(h%hdr.numBuckets)); err != nil { return nil, err } @@ -1014,7 +1075,7 @@ func (f *File) LookupSymbol(symbol libpf.SymbolName) (*libpf.Symbol, error) { h |= 1 for { var h2 uint32 - if _, err := f.ReadVirtualMemory(pfunsafe.FromPointer(&h2), offs); err != nil { + if _, err := f.ReadAt(pfunsafe.FromPointer(&h2), offs); err != nil { return nil, err } // Do a full match of the symbol if the symbol hash matches @@ -1034,7 +1095,7 @@ func (f *File) LookupSymbol(symbol libpf.SymbolName) (*libpf.Symbol, error) { // Normal ELF symbol lookup. Refer to ELF spec, part 2 "Hash Table" (2-19) hdr := &f.sysvHash.header if hdr.numBuckets == 0 { - if _, err := f.ReadVirtualMemory(pfunsafe.FromPointer(hdr), f.sysvHash.addr); err != nil { + if _, err := f.ReadAt(pfunsafe.FromPointer(hdr), f.sysvHash.addr); err != nil { return nil, err } if hdr.numBuckets == 0 { @@ -1045,7 +1106,7 @@ func (f *File) LookupSymbol(symbol libpf.SymbolName) (*libpf.Symbol, error) { offs := f.sysvHash.addr + int64(unsafe.Sizeof(*hdr)) h := calcSysvHash(symbol) bucket := int64(h % hdr.numBuckets) - if _, err := f.ReadVirtualMemory(pfunsafe.FromPointer(&i), offs+4*bucket); err != nil { + if _, err := f.ReadAt(pfunsafe.FromPointer(&i), offs+4*bucket); err != nil { return nil, err } offs += 4 * int64(hdr.numBuckets) @@ -1053,7 +1114,7 @@ func (f *File) LookupSymbol(symbol libpf.SymbolName) (*libpf.Symbol, error) { if s, ok := f.readAndMatchSymbol(i, symbol); ok { return &s, nil } - if _, err := f.ReadVirtualMemory(pfunsafe.FromPointer(&i), offs+4*int64(i)); err != nil { + if _, err := f.ReadAt(pfunsafe.FromPointer(&i), offs+4*int64(i)); err != nil { return nil, err } } diff --git a/libpf/pfelf/internal/mmap/mmap.go b/libpf/pfelf/internal/mmap/mmap.go index fb648078b..984fc8e4d 100644 --- a/libpf/pfelf/internal/mmap/mmap.go +++ b/libpf/pfelf/internal/mmap/mmap.go @@ -90,6 +90,12 @@ func Open(filename string) (*ReaderAt, error) { return nil, err } defer f.Close() + + return OpenFile(f) +} + +// OpenFile memory-maps the OS file for reading. +func OpenFile(f *os.File) (*ReaderAt, error) { fi, err := f.Stat() if err != nil { return nil, err @@ -107,10 +113,10 @@ func Open(filename string) (*ReaderAt, error) { }, nil } if size < 0 { - return nil, fmt.Errorf("mmap: file %q has negative size", filename) + return nil, fmt.Errorf("mmap: negative file size") } if size != int64(int(size)) { - return nil, fmt.Errorf("mmap: file %q is too large", filename) + return nil, fmt.Errorf("mmap: too large file size") } data, err := syscall.Mmap(int(f.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED) diff --git a/libpf/trace.go b/libpf/trace.go index 9e79e08db..172bc8e37 100644 --- a/libpf/trace.go +++ b/libpf/trace.go @@ -113,7 +113,7 @@ type EbpfTrace struct { PID PID TID PID Origin Origin - OffTime int64 // Time a task was off-cpu in nanoseconds. + Value int64 APMTraceID APMTraceID APMTransactionID APMTransactionID CPU int diff --git a/metrics/ids.go b/metrics/ids.go index 3bfb01859..76ed4dabe 100644 --- a/metrics/ids.go +++ b/metrics/ids.go @@ -647,6 +647,9 @@ const ( // Number of failed attempts to read a CME by exceeding max EP checks IDUnwindRubyErrCmeMaxEp = 285 + // Number of failures to read TLS variables via the DTV + IDUnwindErrBadDTVRead = 286 + // max number of ID values, keep this as *last entry* - IDMax = 286 + IDMax = 287 ) diff --git a/metrics/metrics.go b/metrics/metrics.go index ac67ff236..198ff9f9c 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -49,7 +49,7 @@ func Start(meter metric.Meter) { defs := GetDefinitions() metricTypes = make(map[MetricID]MetricType, len(defs)) for _, md := range defs { - if md.Obsolete { + if md.Obsolete || md.Field == "" { continue } metricTypes[md.ID] = md.Type diff --git a/metrics/metrics.json b/metrics/metrics.json index 83694b424..1021d7414 100644 --- a/metrics/metrics.json +++ b/metrics/metrics.json @@ -2089,5 +2089,12 @@ "name": "UnwindRubyErrCmeMaxEp", "field": "bpf.ruby.errors.read_cme_max_ep", "id": 285 + }, + { + "description": "Number of failures to read TLS variables via the DTV", + "type": "counter", + "name": "UnwindErrBadDTVRead", + "field": "bpf.errors.bad_dtv_read", + "id": 286 } ] diff --git a/nativeunwind/elfunwindinfo/elfehframe_test.go b/nativeunwind/elfunwindinfo/elfehframe_test.go index e0d0cc557..427d31cdd 100644 --- a/nativeunwind/elfunwindinfo/elfehframe_test.go +++ b/nativeunwind/elfunwindinfo/elfehframe_test.go @@ -178,3 +178,71 @@ func TestParseCIE(t *testing.T) { }) } } + +func TestGetUnwindInfoX86_RegisterRA(t *testing.T) { + tests := []struct { + name string + regs vmRegs + expected sdtypes.UnwindInfo + }{ + { + name: "Standard RA=CFA-8", + regs: vmRegs{ + cfa: vmReg{reg: x86RegRSP, off: 16}, // rsp+16 + ra: vmReg{reg: regCFA, off: -8}, + fp: vmReg{reg: regCFA, off: -16}, + }, + expected: sdtypes.UnwindInfo{ + Flags: 0, + BaseReg: support.UnwindRegSp, + Param: 16, + AuxBaseReg: support.UnwindRegCfa, + AuxParam: -16, + }, + }, + { + name: "Register-based RA (RDI)", + regs: vmRegs{ + cfa: vmReg{reg: x86RegRSP, off: 8}, // rsp+8 + ra: vmReg{reg: x86RegRDI, off: 0}, + fp: vmReg{reg: regCFA, off: 0}, + }, + expected: sdtypes.UnwindInfo{ + Flags: support.UnwindFlagRegisterRA | support.UnwindFlagLeafOnly, + BaseReg: support.UnwindRegSp, + Param: 8, + AuxBaseReg: support.UnwindRegX86RDI, + }, + }, + { + name: "Invalid RA", + regs: vmRegs{ + cfa: vmReg{reg: x86RegRSP, off: 20}, + ra: vmReg{reg: regCFA, off: -16}, // Not -8 + fp: vmReg{reg: regCFA, off: 0}, + }, + expected: sdtypes.UnwindInfoInvalid, + }, + { + name: "Exact __vfork FDE: CFA=RSP+0 with RA=RDI", + regs: vmRegs{ + cfa: vmReg{reg: x86RegRSP, off: 0}, // DW_CFA_def_cfa_offset: 0 + ra: vmReg{reg: x86RegRDI, off: 0}, // DW_CFA_register: r16 in r5 + fp: vmReg{reg: regUndefined}, // FP not specified + }, + expected: sdtypes.UnwindInfo{ + Flags: support.UnwindFlagRegisterRA | support.UnwindFlagLeafOnly, + BaseReg: support.UnwindRegSp, + Param: 0, + AuxBaseReg: support.UnwindRegX86RDI, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := tt.regs.getUnwindInfoX86() + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/nativeunwind/elfunwindinfo/elfehframe_x86.go b/nativeunwind/elfunwindinfo/elfehframe_x86.go index fb060b66e..73074a571 100644 --- a/nativeunwind/elfunwindinfo/elfehframe_x86.go +++ b/nativeunwind/elfunwindinfo/elfehframe_x86.go @@ -93,16 +93,22 @@ func getUnwinderRegX86(reg uleb128) uint8 { switch reg { case x86RegRAX: return support.UnwindRegX86RAX + case x86RegRDI: + return support.UnwindRegX86RDI + case x86RegRBP: + return support.UnwindRegFp + case x86RegRSP: + return support.UnwindRegSp + case x86RegR8: + return support.UnwindRegX86R8 case x86RegR9: return support.UnwindRegX86R9 case x86RegR11: return support.UnwindRegX86R11 + case x86RegR13: + return support.UnwindRegX86R13 case x86RegR15: return support.UnwindRegX86R15 - case x86RegRBP: - return support.UnwindRegFp - case x86RegRSP: - return support.UnwindRegSp case x86RegRIP: return support.UnwindRegPc case regCFA: @@ -126,33 +132,39 @@ func (regs *vmRegs) getUnwindInfoX86() sdtypes.UnwindInfo { // condition in samples is statistically unlikely. return sdtypes.UnwindInfoStop } - // Filter invalid RSP based CFAs - if regs.cfa.reg == x86RegRSP && regs.cfa.off == 0 { - return sdtypes.UnwindInfoInvalid - } + info := sdtypes.UnwindInfo{} - // The CFI allows having Return Address (RA) be recoverable via an expression, - // but the eBPF currently supports the ABI standard RA=CFA-8 only. Verify that - // we are not in any weird hand woven assembly which is not supported. - if regs.ra.reg != regCFA || regs.ra.off != -8 { + // The Return Address (RA) is usually recoverable via an ABI standard RA=CFA-8. + // But some functions (like __vfork) use a register to store the RA. + raReg := getUnwinderRegX86(regs.ra.reg) + if raReg != support.UnwindRegInvalid && raReg != support.UnwindRegCfa { + info.Flags |= support.UnwindFlagRegisterRA + info.AuxBaseReg = raReg + if raReg != support.UnwindRegFp && raReg != support.UnwindRegSp { + info.Flags |= support.UnwindFlagLeafOnly + } + } else if regs.ra.reg == regCFA && regs.ra.off == -8 { + // Standard CFA-8 + } else { + // Not the standard CFA-8 and not a supported register return sdtypes.UnwindInfoInvalid } - info := sdtypes.UnwindInfo{} - // Determine unwind info for frame pointer - switch regs.fp.reg { - case regCFA: - // Check that RBP is between CFA and stack top - if regs.cfa.reg != x86RegRSP || (regs.fp.off < 0 && regs.fp.off >= -regs.cfa.off) { - info.AuxBaseReg = support.UnwindRegCfa - info.AuxParam = int32(regs.fp.off) - } - case regExprReg: - // expression: RBP+offrbp - if r, _, offrbp, _ := splitOff(regs.fp.off); uleb128(r) == x86RegRBP { - info.AuxBaseReg = support.UnwindRegFp - info.AuxParam = int32(offrbp) + if info.Flags&support.UnwindFlagRegisterRA == 0 { + switch regs.fp.reg { + case regCFA: + // Check that RBP is between CFA and stack top + if regs.cfa.reg != x86RegRSP || (regs.fp.off < 0 && regs.fp.off >= -regs.cfa.off) { + info.AuxBaseReg = support.UnwindRegCfa + info.AuxParam = int32(regs.fp.off) + } + case regExprReg: + // expression: RBP+offrbp + if r, _, offrbp, _ := splitOff(regs.fp.off); uleb128(r) == x86RegRBP { + info.AuxBaseReg = support.UnwindRegFp + info.AuxParam = int32(offrbp) + } } } diff --git a/nativeunwind/elfunwindinfo/stackdeltaextraction.go b/nativeunwind/elfunwindinfo/stackdeltaextraction.go index 0bc4b63c2..6108d00d7 100644 --- a/nativeunwind/elfunwindinfo/stackdeltaextraction.go +++ b/nativeunwind/elfunwindinfo/stackdeltaextraction.go @@ -127,10 +127,13 @@ func (ee *elfExtractor) extractDebugDeltas() (err error) { return err } -func isLibCrypto(elfFile *pfelf.File) bool { +func isLibGenericRegsAllowed(elfFile *pfelf.File) bool { if name, err := elfFile.DynString(elf.DT_SONAME); err == nil && len(name) == 1 { - // Allow generic register CFA for openssl libcrypto - return strings.HasPrefix(name[0], "libcrypto.so.") + // Allow generic register CFA for openssl libcrypto, glibc and musl + n := name[0] + return strings.HasPrefix(n, "libcrypto.so.") || + strings.HasPrefix(n, "libc.so.") || + strings.HasPrefix(n, "ld-linux-") } return false } @@ -191,7 +194,7 @@ func extractFile(elfFile *pfelf.File, elfRef *pfelf.Reference, file: elfFile, deltas: &deltas, hooks: &filter, - allowGenericRegs: isLibCrypto(elfFile), + allowGenericRegs: isLibGenericRegsAllowed(elfFile), } if entryLength := detectEntry(elfFile); entryLength != 0 { diff --git a/nativeunwind/stackdeltatypes/stackdeltatypes.go b/nativeunwind/stackdeltatypes/stackdeltatypes.go index fd4f0ed11..334e9fa75 100644 --- a/nativeunwind/stackdeltatypes/stackdeltatypes.go +++ b/nativeunwind/stackdeltatypes/stackdeltatypes.go @@ -76,6 +76,15 @@ type IntervalData struct { func (deltas *StackDeltaArray) AddEx(delta StackDelta, sorted bool) { num := len(*deltas) if delta.Info.Flags&support.UnwindFlagCommand != 0 { + if delta.Info.Param == support.UnwindCommandSignal { + // EBPF code does a -1 fixup for return addresses. + // To match the signal handler function injected into + // stack, the signal handler stack delta must start one + // byte earlier to accommodate for the ebpf fixup. + // C-libraries will have a 'nop' inserted to make sure + // nothing conflicts. + delta.Address-- + } // FP information is invalid/unused for command opcodes. // But DWARF info often leaves bogus data there, so resetting it // reduces the number of unique Info contents generated. diff --git a/process/coredump.go b/process/coredump.go index 385ce52d2..053fa7ccb 100644 --- a/process/coredump.go +++ b/process/coredump.go @@ -46,7 +46,7 @@ type CoredumpProcess struct { machineData MachineData // mappings contains the parsed mappings. - mappings []Mapping + mappings []RawMapping // threadInfo contains the parsed thread info. threadInfo []ThreadInfo @@ -145,7 +145,7 @@ func OpenCoredumpFile(f *pfelf.File) (*CoredumpProcess, error) { cd := &CoredumpProcess{ File: f, files: make(map[string]*CoredumpFile), - mappings: make([]Mapping, 0, len(f.Progs)), + mappings: make([]RawMapping, 0, len(f.Progs)), threadInfo: make([]ThreadInfo, 0, 8), } cd.machineData.Machine = cd.Machine @@ -157,7 +157,7 @@ func OpenCoredumpFile(f *pfelf.File) (*CoredumpProcess, error) { for i := range f.Progs { p := &f.Progs[i] if p.Type == elf.PT_LOAD && p.Flags != 0 { - m := Mapping{ + m := RawMapping{ Vaddr: p.Vaddr, Length: p.Memsz, Flags: p.Flags, @@ -269,9 +269,14 @@ func (cd *CoredumpProcess) GetExe() (libpf.String, error) { return cd.fname, nil } -// GetMappings implements the Process interface. -func (cd *CoredumpProcess) GetMappings() ([]Mapping, uint32, error) { - return cd.mappings, 0, nil +// IterateMappings implements the Process interface. +func (cd *CoredumpProcess) IterateMappings(callback func(m RawMapping) bool) (uint32, error) { + for _, m := range cd.mappings { + if !callback(m) { + return 0, ErrCallbackStopped + } + } + return 0, nil } // GetThreadInfo implements the Process interface. @@ -280,18 +285,18 @@ func (cd *CoredumpProcess) GetThreads() ([]ThreadInfo, error) { } // OpenMappingFile implements the Process interface. -func (cd *CoredumpProcess) OpenMappingFile(_ *Mapping) (ReadAtCloser, error) { +func (cd *CoredumpProcess) OpenMappingFile(_ *RawMapping) (ReadAtCloser, error) { // Coredumps do not contain the original backing files. return nil, errors.New("coredump does not support opening backing file") } // GetMappingFileLastModified implements the Process interface. -func (cd *CoredumpProcess) GetMappingFileLastModified(_ *Mapping) int64 { +func (cd *CoredumpProcess) GetMappingFileLastModified(_ *RawMapping) int64 { return 0 } // CalculateMappingFileID implements the Process interface. -func (cd *CoredumpProcess) CalculateMappingFileID(m *Mapping) (libpf.FileID, error) { +func (cd *CoredumpProcess) CalculateMappingFileID(m *RawMapping) (libpf.FileID, error) { // It is not possible to calculate the real FileID as the section headers // are likely missing. So just return a synthesized FileID. vaddr := make([]byte, 8) @@ -299,7 +304,7 @@ func (cd *CoredumpProcess) CalculateMappingFileID(m *Mapping) (libpf.FileID, err h := fnv.New128a() _, _ = h.Write(vaddr) - _, _ = h.Write([]byte(m.Path.String())) + _, _ = h.Write([]byte(m.Path)) return libpf.FileIDFromBytes(h.Sum(nil)) } @@ -399,7 +404,7 @@ func (cd *CoredumpProcess) parseMappings(desc []byte, cf.Mappings = append(cf.Mappings, cm) mapping := &cd.mappings[m.mappingIndex] - mapping.Path = cf.Name + mapping.Path = cf.Name.String() mapping.FileOffset = entry.FileOffset * hdr.PageSize // Synthesize non-zero device and inode indicating this is a filebacked mapping. mapping.Device = 1 @@ -408,14 +413,14 @@ func (cd *CoredumpProcess) parseMappings(desc []byte, // This file backed mapping is not in the coredump LOAD tables // Likely a executable mapping excluded by core_filter. Construct // the mappings assuming R+X. - cd.mappings = append(cd.mappings, Mapping{ + cd.mappings = append(cd.mappings, RawMapping{ Vaddr: entry.Start, Length: entry.End - entry.Start, Flags: elf.PF_R + elf.PF_X, FileOffset: entry.FileOffset * hdr.PageSize, Device: 1, Inode: cf.inode, - Path: cf.Name, + Path: cf.Name.String(), }) } strs = strs[fnlen+1:] @@ -438,7 +443,7 @@ func (cd *CoredumpProcess) parseAuxVector(desc []byte, vaddrToMappings map[uint6 vm.Inode = vdsoInode vm.Path = VdsoPathName - cf := cd.getFile(vm.Path.String()) + cf := cd.getFile(vm.Path) cm := CoredumpMapping{ Prog: m.prog, File: cf, diff --git a/process/process.go b/process/process.go index 6901a95e3..54878b8d1 100644 --- a/process/process.go +++ b/process/process.go @@ -10,8 +10,10 @@ import ( "errors" "fmt" "io" + "io/fs" "os" "path" + "path/filepath" "regexp" "strconv" "strings" @@ -27,9 +29,13 @@ import ( "go.opentelemetry.io/ebpf-profiler/stringutil" ) -// GetMappings returns this error when no mappings can be extracted. +// ErrNoMappings is returned when no mappings can be extracted. var ErrNoMappings = errors.New("no mappings") +// ErrCallbackStopped is returned when the IterateMappings callback returns +// false, signaling that iteration was intentionally interrupted. +var ErrCallbackStopped = errors.New("IterateMappings stopped by callback") + const ( containerSource = "[0-9a-f]{64}" taskSource = "[0-9a-f]{32}-\\d+" @@ -55,7 +61,7 @@ type systemProcess struct { mainThreadExit bool remoteMemory remotememory.RemoteMemory - fileToMapping map[string]*Mapping + fileToMapping map[string]*RawMapping } var _ Process = &systemProcess{} @@ -177,6 +183,62 @@ func extractContainerID(pid libpf.PID) (libpf.String, error) { return parseContainerID(cgroupFile), nil } +// CgroupRootInode returns the inode of /proc//root/sys/fs/cgroup, which identifies +// the cgroup namespace root visible to the given process, unaffected by namespace masking. +func CgroupRootInode(pid libpf.PID) (uint64, error) { + var st unix.Stat_t + if err := unix.Stat(fmt.Sprintf("/proc/%d/root/sys/fs/cgroup", pid), &st); err != nil { + return 0, err + } + return st.Ino, nil +} + +// DetectSelfContainerIDViaInode detects the current process's container ID by matching +// cgroup directory inodes. When the process runs in a private cgroup namespace (cgroup v2), +// /proc/self/cgroup returns a path relative to the namespace root (e.g. "0::/"), making it +// impossible to extract the container ID via the standard path. However, stat("/sys/fs/cgroup") +// returns the inode of the process's actual cgroup directory on the host, unaffected by +// namespace masking. This function walks the host's cgroup tree (via +// /proc/1/root/sys/fs/cgroup) to find the directory whose inode matches, then extracts +// the container ID from its path. +func DetectSelfContainerIDViaInode() (libpf.String, uint64, error) { + const hostCgroupRoot = "/proc/1/root/sys/fs/cgroup" + + var selfStat unix.Stat_t + if err := unix.Stat("/sys/fs/cgroup", &selfStat); err != nil { + return libpf.NullString, 0, fmt.Errorf("failed to stat /sys/fs/cgroup: %w", err) + } + selfIno := selfStat.Ino + + var matched libpf.String + err := filepath.WalkDir(hostCgroupRoot, func(path string, d fs.DirEntry, err error) error { + if err != nil { + if d == nil { + return err // root is inaccessible + } + return nil // skip inaccessible subdirectories + } + if !d.IsDir() { + return nil + } + var st unix.Stat_t + if err := unix.Stat(path, &st); err != nil { + return nil + } + if st.Ino == selfIno { + if parts := expContainerID.FindStringSubmatch(path); len(parts) == 2 { + matched = libpf.Intern(parts[1]) + } + return filepath.SkipAll + } + return nil + }) + if err != nil { + return libpf.NullString, 0, fmt.Errorf("failed to walk host cgroup tree: %w", err) + } + return matched, selfIno, nil +} + func trimMappingPath(path string) string { // Trim the deleted indication from the path. // See path_with_deleted in linux/fs/d_path.c @@ -189,13 +251,12 @@ func trimMappingPath(path string) string { return path } -func parseMappings(mapsFile io.Reader) ([]Mapping, uint32, error) { +func iterateMappings(mapsFile io.Reader, callback func(m RawMapping) bool) (uint32, error) { numParseErrors := uint32(0) - mappings := make([]Mapping, 0, 32) scanner := bufio.NewScanner(mapsFile) scanBuf := bufPool.Get().(*[]byte) if scanBuf == nil { - return mappings, 0, errors.New("failed to get memory from sync pool") + return 0, errors.New("failed to get memory from sync pool") } defer func() { // Reset memory and return it for reuse. @@ -211,6 +272,10 @@ func parseMappings(mapsFile io.Reader) ([]Mapping, uint32, error) { var addrs [2]string var devs [2]string + // WARNING: line (and all substrings derived from it, including the + // Path field of the emitted RawMapping) points into scanBuf which is + // recycled after iteration. Callers must intern Path (libpf.Intern) + // before storing. line := pfunsafe.ToString(scanner.Bytes()) if stringutil.FieldsN(line, fields[:]) < 5 { numParseErrors++ @@ -266,7 +331,7 @@ func parseMappings(mapsFile io.Reader) ([]Mapping, uint32, error) { } device := major<<8 + minor - var path libpf.String + var path string if inode == 0 { if fields[5] == "[vdso]" { // Map to something filename looking with synthesized inode @@ -275,12 +340,15 @@ func parseMappings(mapsFile io.Reader) ([]Mapping, uint32, error) { inode = vdsoInode } else if fields[5] == "" { // This is an anonymous mapping, keep it + } else if strings.HasPrefix(fields[5], "[anon:") { + // Keep named anonymous mapping + path = fields[5] } else { // Ignore other mappings that are invalid, non-existent or are special pseudo-files continue } } else { - path = libpf.Intern(trimMappingPath(fields[5])) + path = trimMappingPath(fields[5]) } vaddr, err := strconv.ParseUint(addrs[0], 16, 64) @@ -304,7 +372,7 @@ func parseMappings(mapsFile io.Reader) ([]Mapping, uint32, error) { continue } - mappings = append(mappings, Mapping{ + if !callback(RawMapping{ Vaddr: vaddr, Length: length, Flags: flags, @@ -312,29 +380,39 @@ func parseMappings(mapsFile io.Reader) ([]Mapping, uint32, error) { Device: device, Inode: inode, Path: path, - }) + }) { + return numParseErrors, ErrCallbackStopped + } } - return mappings, numParseErrors, scanner.Err() + return numParseErrors, scanner.Err() } -// GetMappings will process the mappings file from proc. Additionally, -// a reverse map from mapping filename to a Mapping node is built to allow -// OpenELF opening ELF files using the corresponding proc map_files entry. -// WARNING: This implementation does not support calling GetMappings -// concurrently with itself, or with OpenELF. -func (sp *systemProcess) GetMappings() ([]Mapping, uint32, error) { +func (sp *systemProcess) IterateMappings(callback func(m RawMapping) bool) (uint32, error) { mapsFile, err := os.Open(fmt.Sprintf("/proc/%d/maps", sp.pid)) if err != nil { - return nil, 0, err + return 0, err } defer mapsFile.Close() - mappings, numParseErrors, err := parseMappings(mapsFile) + fileToMapping := make(map[string]*RawMapping) + gotMappings := false + + collectForOpenELF := func(m RawMapping) bool { + gotMappings = true + if m.IsExecutable() || m.IsVDSO() { + stored := m + stored.Path = libpf.Intern(m.Path).String() + fileToMapping[stored.Path] = &stored + } + return callback(m) + } + + numParseErrors, err := iterateMappings(mapsFile, collectForOpenELF) if err != nil { - return mappings, numParseErrors, err + return numParseErrors, err } - if len(mappings) == 0 { + if !gotMappings { // We could test for main thread exit here by checking for zombie state // in /proc/sp.pid/stat but it's simpler to assume that this is the case // and try extracting mappings for a different thread. Since we stopped @@ -344,7 +422,7 @@ func (sp *systemProcess) GetMappings() ([]Mapping, uint32, error) { sp.mainThreadExit = true if sp.pid == sp.tid { - return mappings, numParseErrors, ErrNoMappings + return numParseErrors, ErrNoMappings } log.Debugf("TID: %v extracting mappings", sp.tid) @@ -356,24 +434,17 @@ func (sp *systemProcess) GetMappings() ([]Mapping, uint32, error) { // the agent to unload process metadata when a thread exits but the process is still // alive). if err != nil { - return mappings, numParseErrors, ErrNoMappings + return numParseErrors, ErrNoMappings } defer mapsFileAlt.Close() - mappings, numParseErrors, err = parseMappings(mapsFileAlt) - if err != nil || len(mappings) == 0 { - return mappings, numParseErrors, ErrNoMappings + numParseErrors, err := iterateMappings(mapsFileAlt, collectForOpenELF) + if err != nil || !gotMappings { + return numParseErrors, ErrNoMappings } } - fileToMapping := make(map[string]*Mapping) - for idx := range mappings { - m := &mappings[idx] - if m.Path != libpf.NullString { - fileToMapping[m.Path.String()] = m - } - } sp.fileToMapping = fileToMapping - return mappings, numParseErrors, nil + return numParseErrors, nil } func (sp *systemProcess) GetThreads() ([]ThreadInfo, error) { @@ -388,7 +459,7 @@ func (sp *systemProcess) GetRemoteMemory() remotememory.RemoteMemory { return sp.remoteMemory } -func (sp *systemProcess) extractMapping(m *Mapping) (*bytes.Reader, error) { +func (sp *systemProcess) extractMapping(m *RawMapping) (*bytes.Reader, error) { data := make([]byte, m.Length) _, err := sp.remoteMemory.ReadAt(data, int64(m.Vaddr)) if err != nil { @@ -398,8 +469,8 @@ func (sp *systemProcess) extractMapping(m *Mapping) (*bytes.Reader, error) { return bytes.NewReader(data), nil } -func (sp *systemProcess) getMappingFile(m *Mapping) string { - if m.IsAnonymous() || m.IsVDSO() { +func (sp *systemProcess) getMappingFile(m *RawMapping) string { + if !m.IsFileBacked() { return "" } if sp.mainThreadExit { @@ -407,12 +478,12 @@ func (sp *systemProcess) getMappingFile(m *Mapping) string { // nor /proc/sp.pid/root exist if main thread has exited, so we use the // mapping path directly under the sp.tid root. rootPath := fmt.Sprintf("/proc/%v/task/%v/root", sp.pid, sp.tid) - return path.Join(rootPath, m.Path.String()) + return path.Join(rootPath, m.Path) } return fmt.Sprintf("/proc/%v/map_files/%x-%x", sp.pid, m.Vaddr, m.Vaddr+m.Length) } -func (sp *systemProcess) OpenMappingFile(m *Mapping) (ReadAtCloser, error) { +func (sp *systemProcess) OpenMappingFile(m *RawMapping) (ReadAtCloser, error) { filename := sp.getMappingFile(m) if filename == "" { return nil, errors.New("no backing file for anonymous memory") @@ -420,7 +491,7 @@ func (sp *systemProcess) OpenMappingFile(m *Mapping) (ReadAtCloser, error) { return os.Open(filename) } -func (sp *systemProcess) GetMappingFileLastModified(m *Mapping) int64 { +func (sp *systemProcess) GetMappingFileLastModified(m *RawMapping) int64 { filename := sp.getMappingFile(m) if filename != "" { var st unix.Stat_t @@ -435,7 +506,7 @@ func (sp *systemProcess) GetMappingFileLastModified(m *Mapping) int64 { // VDSO for the system. var vdsoFileID libpf.FileID -func (sp *systemProcess) CalculateMappingFileID(m *Mapping) (libpf.FileID, error) { +func (sp *systemProcess) CalculateMappingFileID(m *RawMapping) (libpf.FileID, error) { if m.IsVDSO() { if vdsoFileID != (libpf.FileID{}) { return vdsoFileID, nil diff --git a/process/process_test.go b/process/process_test.go index b7c943c9e..e67ac2593 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -5,6 +5,7 @@ package process import ( "debug/elf" + "io" "os" "runtime" "strings" @@ -30,87 +31,135 @@ var testMappings = `55fe82710000-55fe8273c000 r--p 00000000 fd:01 1068432 7f63c8eef000 r-xp 0001c000 1fd:01 1075944 7f8b929f0000-7f8b92a00000 r-xp 00000000 00:00 0 ` +var allExpectedMappings = []RawMapping{ + { + Vaddr: 0x55fe82710000, + Device: 0xfd01, + Flags: elf.PF_R, + Inode: 1068432, + Length: 0x2c000, + FileOffset: 0, + Path: "/tmp/usr_bin_seahorse", + }, + { + Vaddr: 0x55fe8273c000, + Device: 0xfd01, + Flags: elf.PF_R + elf.PF_X, + Inode: 1068432, + Length: 0x82000, + FileOffset: 0x2c000, + Path: "/tmp/usr_bin_seahorse", + }, + { + Vaddr: 0x55fe827be000, + Device: 0xfd01, + Flags: elf.PF_R, + Inode: 1068432, + Length: 0x78000, + FileOffset: 0xae000, + Path: "/tmp/usr_bin_seahorse", + }, + { + Vaddr: 0x55fe82836000, + Device: 0xfd01, + Flags: elf.PF_R, + Inode: 1068432, + Length: 0x7000, + FileOffset: 0x125000, + Path: "/tmp/usr_bin_seahorse", + }, + { + Vaddr: 0x55fe8283d000, + Device: 0xfd01, + Flags: elf.PF_R + elf.PF_W, + Inode: 1068432, + Length: 0x1000, + FileOffset: 0x12c000, + Path: "/tmp/usr_bin_seahorse", + }, + { + Vaddr: 0x7f63c8c3e000, + Device: 0x0801, + Flags: elf.PF_R + elf.PF_X, + Inode: 1048922, + Length: 0x1A2000, + FileOffset: 544768, + Path: "/tmp/usr_lib_x86_64-linux-gnu_libcrypto.so.1.1", + }, + { + Vaddr: 0x7f63c8ebf000, + Device: 0x1fd01, + Flags: elf.PF_R + elf.PF_X, + Inode: 1075944, + Length: 0x130000, + FileOffset: 114688, + Path: "/tmp/usr_lib_x86_64-linux-gnu_libopensc.so.6.0.0", + }, + { + Vaddr: 0x7f8b929f0000, + Device: 0x0, + Flags: elf.PF_R + elf.PF_X, + Inode: 0, + Length: 0x10000, + FileOffset: 0, + Path: "", + }, +} + +func getTestMappings(t *testing.T, mapsFile io.Reader) ([]RawMapping, uint32, error) { + t.Helper() + + mappings := make([]RawMapping, 0, 32) + numParseErrors, err := iterateMappings(mapsFile, func(m RawMapping) bool { + m.Path = libpf.Intern(m.Path).String() + mappings = append(mappings, m) + return true + }) + return mappings, numParseErrors, err +} + +func getTestMappingsFromProcess(t *testing.T, process Process) ([]RawMapping, uint32, error) { + t.Helper() + + mappings := make([]RawMapping, 0, 32) + numParseErrors, err := process.IterateMappings(func(m RawMapping) bool { + m.Path = libpf.Intern(m.Path).String() + mappings = append(mappings, m) + return true + }) + return mappings, numParseErrors, err +} + func TestParseMappings(t *testing.T) { - mappings, numParseErrors, err := parseMappings(strings.NewReader(testMappings)) + mappings, numParseErrors, err := getTestMappings(t, strings.NewReader(testMappings)) require.NoError(t, err) require.Equal(t, uint32(4), numParseErrors) - assert.NotNil(t, mappings) + assert.Equal(t, allExpectedMappings, mappings) +} - expected := []Mapping{ - { - Vaddr: 0x55fe82710000, - Device: 0xfd01, - Flags: elf.PF_R, - Inode: 1068432, - Length: 0x2c000, - FileOffset: 0, - Path: libpf.Intern("/tmp/usr_bin_seahorse"), - }, - { - Vaddr: 0x55fe8273c000, - Device: 0xfd01, - Flags: elf.PF_R + elf.PF_X, - Inode: 1068432, - Length: 0x82000, - FileOffset: 0x2c000, - Path: libpf.Intern("/tmp/usr_bin_seahorse"), - }, - { - Vaddr: 0x55fe827be000, - Device: 0xfd01, - Flags: elf.PF_R, - Inode: 1068432, - Length: 0x78000, - FileOffset: 0xae000, - Path: libpf.Intern("/tmp/usr_bin_seahorse"), - }, - { - Vaddr: 0x55fe82836000, - Device: 0xfd01, - Flags: elf.PF_R, - Inode: 1068432, - Length: 0x7000, - FileOffset: 0x125000, - Path: libpf.Intern("/tmp/usr_bin_seahorse"), - }, - { - Vaddr: 0x55fe8283d000, - Device: 0xfd01, - Flags: elf.PF_R + elf.PF_W, - Inode: 1068432, - Length: 0x1000, - FileOffset: 0x12c000, - Path: libpf.Intern("/tmp/usr_bin_seahorse"), - }, - { - Vaddr: 0x7f63c8c3e000, - Device: 0x0801, - Flags: elf.PF_R + elf.PF_X, - Inode: 1048922, - Length: 0x1A2000, - FileOffset: 544768, - Path: libpf.Intern("/tmp/usr_lib_x86_64-linux-gnu_libcrypto.so.1.1"), - }, - { - Vaddr: 0x7f63c8ebf000, - Device: 0x1fd01, - Flags: elf.PF_R + elf.PF_X, - Inode: 1075944, - Length: 0x130000, - FileOffset: 114688, - Path: libpf.Intern("/tmp/usr_lib_x86_64-linux-gnu_libopensc.so.6.0.0"), - }, - { - Vaddr: 0x7f8b929f0000, - Device: 0x0, - Flags: elf.PF_R + elf.PF_X, - Inode: 0, - Length: 0x10000, - FileOffset: 0, - Path: libpf.NullString, - }, +func TestMappingPredicates(t *testing.T) { + tests := []struct { + name string + m RawMapping + wantAnon bool + wantFile bool + wantMemFD bool + wantVDSO bool + }{ + {"anonymous", RawMapping{}, true, false, false, false}, + {"file-backed", RawMapping{Path: "/usr/lib/foo.so"}, false, true, false, false}, + {"memfd", RawMapping{Path: "/memfd:jit"}, true, false, true, false}, + {"vdso", RawMapping{Path: VdsoPathName}, false, false, false, true}, + {"/dev/zero normalized", RawMapping{Inode: 42, Device: 1}, true, false, false, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.wantAnon, tt.m.IsAnonymous(), "IsAnonymous") + assert.Equal(t, tt.wantFile, tt.m.IsFileBacked(), "IsFileBacked") + assert.Equal(t, tt.wantMemFD, tt.m.IsMemFD(), "IsMemFD") + assert.Equal(t, tt.wantVDSO, tt.m.IsVDSO(), "IsVDSO") + }) } - assert.Equal(t, expected, mappings) } func TestNewPIDOfSelf(t *testing.T) { @@ -121,7 +170,7 @@ func TestNewPIDOfSelf(t *testing.T) { pr := New(pid, pid) assert.NotNil(t, pr) - mappings, numParseErrors, err := pr.GetMappings() + mappings, numParseErrors, err := getTestMappingsFromProcess(t, pr) require.NoError(t, err) require.Equal(t, uint32(0), numParseErrors) assert.NotEmpty(t, mappings) diff --git a/process/types.go b/process/types.go index 54efc0dea..3fbd6860f 100644 --- a/process/types.go +++ b/process/types.go @@ -12,18 +12,25 @@ import ( "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" + "go.opentelemetry.io/ebpf-profiler/processcontext" "go.opentelemetry.io/ebpf-profiler/remotememory" "go.opentelemetry.io/ebpf-profiler/util" ) // VdsoPathName is the path to use for VDSO mappings. -var VdsoPathName = libpf.Intern("linux-vdso.1.so") +const VdsoPathName = "linux-vdso.1.so" // vdsoInode is the synthesized inode number for VDSO mappings. const vdsoInode = 50 -// Mapping contains information about a memory mapping. -type Mapping struct { +// RawMapping represents a memory mapping parsed from /proc/pid/maps or a coredump. +// +// WARNING: When produced by the systemProcess IterateMappings implementation, +// Path may reference an internal scanner buffer that is recycled after the +// iteration completes. Callers that need to store the mapping beyond the +// callback scope must intern the Path via libpf.Intern to detach it from the +// buffer and deduplicate identical paths across mappings. +type RawMapping struct { // Vaddr is the virtual memory start for this mapping. Vaddr uint64 // Length is the length of the mapping. @@ -36,27 +43,34 @@ type Mapping struct { Device uint64 // Inode holds the mapped file's inode number. Inode uint64 - // Path contains the file name for file backed mappings. - Path libpf.String + // Path is the file path for file-backed and special mappings. + // When received from IterateMappings, this may point into an internal + // buffer. The caller must use libpf.Intern to detach it before storing + // the mapping long-term. + Path string } -func (m *Mapping) IsExecutable() bool { +func (m *RawMapping) IsExecutable() bool { return m.Flags&elf.PF_X == elf.PF_X } -func (m *Mapping) IsAnonymous() bool { - return m.Path == libpf.NullString || m.IsMemFD() +func (m *RawMapping) IsAnonymous() bool { + return !m.IsFileBacked() && !m.IsVDSO() } -func (m *Mapping) IsMemFD() bool { - return strings.HasPrefix(m.Path.String(), "/memfd:") +func (m *RawMapping) IsFileBacked() bool { + return m.Path != "" && !m.IsVDSO() && !m.IsMemFD() } -func (m *Mapping) IsVDSO() bool { +func (m *RawMapping) IsMemFD() bool { + return strings.HasPrefix(m.Path, "/memfd:") +} + +func (m *RawMapping) IsVDSO() bool { return m.Path == VdsoPathName } -func (m *Mapping) GetOnDiskFileIdentifier() util.OnDiskFileIdentifier { +func (m *RawMapping) GetOnDiskFileIdentifier() util.OnDiskFileIdentifier { return util.OnDiskFileIdentifier{ DeviceID: m.Device, InodeNum: m.Inode, @@ -105,6 +119,8 @@ type ProcessMeta struct { EnvVariables map[libpf.String]libpf.String // container ID retrieved from /proc/PID/cgroup ContainerID libpf.String + // process context + ProcessContextInfo processcontext.Info } // Process is the interface to inspect ELF coredump/process. @@ -124,8 +140,13 @@ type Process interface { // GetExe returns the executable path of the process. GetExe() (libpf.String, error) - // GetMappings reads and parses process memory mappings. - GetMappings() ([]Mapping, uint32, error) + // IterateMappings parses process memory mappings and calls the + // callback for each mapping. The RawMapping's Path field may reference + // an internal buffer that is recycled after the iteration completes; + // callers must use libpf.Intern to detach the Path before storing the + // mapping beyond the callback scope. Returning false from the callback + // stops iteration and causes ErrCallbackStopped to be returned. + IterateMappings(callback func(m RawMapping) bool) (uint32, error) // GetThreads reads the process thread states. GetThreads() ([]ThreadInfo, error) @@ -134,14 +155,14 @@ type Process interface { GetRemoteMemory() remotememory.RemoteMemory // OpenMappingFile returns ReadAtCloser accessing the backing file of the mapping. - OpenMappingFile(*Mapping) (ReadAtCloser, error) + OpenMappingFile(*RawMapping) (ReadAtCloser, error) // GetMappingFileLastModifed returns the timestamp when the backing file was last modified // or zero if an error occurs or mapping file is not accessible via filesystem. - GetMappingFileLastModified(*Mapping) int64 + GetMappingFileLastModified(*RawMapping) int64 // CalculateMappingFileID calculates FileID of the backing file. - CalculateMappingFileID(*Mapping) (libpf.FileID, error) + CalculateMappingFileID(*RawMapping) (libpf.FileID, error) io.Closer diff --git a/processcontext/processcontext.go b/processcontext/processcontext.go new file mode 100644 index 000000000..790f95803 --- /dev/null +++ b/processcontext/processcontext.go @@ -0,0 +1,210 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package processcontext // import "go.opentelemetry.io/ebpf-profiler/processcontext" + +import ( + "encoding/binary" + "errors" + "fmt" + "structs" + "unsafe" + + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/ebpf-profiler/libpf" + "go.opentelemetry.io/ebpf-profiler/libpf/pfunsafe" + processcontextpb "go.opentelemetry.io/ebpf-profiler/processcontext/v1development" + "go.opentelemetry.io/ebpf-profiler/remotememory" +) + +const ( + // OTel process context is published in a mapping: + // - based on a memfd file descriptor named "OTEL_CTX" when memfd_create is available. + // - based on an anonymous private mapping when memfd_create is not available + // In both cases, an attempt is made to name the mapping "OTEL_CTX" using prctl(PR_SET_VMA_ANON_NAME) which may fail depending on kernel version/configuration. + // Consequently the mapping can show up with 3 different names: + // - "/memfd:OTEL_CTX": memfd-based mapping and prctl failed + // - "[anon_shmem:OTEL_CTX]": memfd-based mapping and prctl succeeded + // - "[anon:OTEL_CTX]": anonymous mapping and prctl succeeded + // Case where both memfd_create and prctl fail is considered a failure and is not supported. + ContextMappingMemfd = "/memfd:OTEL_CTX" + ContextMappingMemfdDeleted = "/memfd:OTEL_CTX (deleted)" + ContextMappingMemfdNamed = "[anon_shmem:OTEL_CTX]" + ContextMappingAnonNamed = "[anon:OTEL_CTX]" + + // default maximum number of read attempts on concurrent updates + defaultMaxAttempts = 3 + + // Signature + signatureOTELCTX = "OTEL_CTX" + + // Expected format version + supportedVersion = 2 + + // Maximum payload size + maxPayloadSize = 65536 + + // Offset of the MonotonicPublishedAtNs field in the header struct + monotonicPublishedAtNsOffset = libpf.Address(unsafe.Offsetof(header{}.MonotonicPublishedAtNs)) +) + +var ( + // ErrInvalidContext indicates the ProcessContext has invalid format, signature, version, or size. + ErrInvalidContext = errors.New("invalid ProcessContext") + + // ErrConcurrentUpdate indicates the ProcessContext was updated during read. + ErrConcurrentUpdate = errors.New("concurrent ProcessContext update detected") + + // ErrNoUpdate indicates the ProcessContext has not been updated since it was last published. + ErrNoUpdate = errors.New("ProcessContext has not been updated") +) + +type Info struct { + Context *processcontextpb.ProcessContext + PublishedAtNs uint64 +} + +// header represents the 32-byte memory region header per OTEP #4719. +type header struct { + _ structs.HostLayout + Signature [8]byte // "OTEL_CTX" + Version uint32 // Format version (2) + PayloadSize uint32 // Size of protobuf payload in bytes + MonotonicPublishedAtNs uint64 // Monotonic clock timestamp from `CLOCK_BOOTTIME` of when the context was published, in nanoseconds + PayloadPtr uint64 // Memory pointer to protobuf payload +} + +// Read reads ProcessContext from remote process memory using the provided address. +// Returns ErrInvalidContext if the process has no ProcessContext memory region. +// Retries on concurrent updates, up to maxAttempts total attempts. +// If maxAttempts is 0, the default value is used. +func Read(addr libpf.Address, rm remotememory.RemoteMemory, lastPublishedAtNs uint64, maxAttempts int) (Info, error) { + if maxAttempts == 0 { + maxAttempts = defaultMaxAttempts + } + var lastErr error + + for range maxAttempts { + processCtx, err := readOnce(addr, rm, lastPublishedAtNs) + if err == nil { + return processCtx, nil + } + if !errors.Is(err, ErrConcurrentUpdate) { + return Info{}, err + } + lastErr = err + } + return Info{}, lastErr +} + +// readOnce performs a single attempt to read ProcessContext. +func readOnce(mappingAddr libpf.Address, rm remotememory.RemoteMemory, lastPublishedAtNs uint64) (Info, error) { + monotonicPublishedAtNs, err := readTimestamp(rm, mappingAddr) + if err != nil { + return Info{}, fmt.Errorf("%w: %w", + ErrInvalidContext, err) + } + if monotonicPublishedAtNs == 0 { + return Info{}, ErrConcurrentUpdate + } + + // Check if the context was published after the last published timestamp + if monotonicPublishedAtNs <= lastPublishedAtNs { + return Info{}, ErrNoUpdate + } + + // Read and validate the header + hdr, err := readHeader(rm, mappingAddr) + if err != nil { + return Info{}, fmt.Errorf("%w: %w", + ErrInvalidContext, err) + } + + // Read the payload + ctx, ctxErr := readPayload(rm, hdr) + // Do not check for errors here as the context read might have failed due to + // a concurrent update occurring between the header read and the payload read. + // We will check for context read error after re-reading the header. + + // Re-read the timestamp to check for concurrent updates + monotonicPublishedAtNs2, err := readTimestamp(rm, mappingAddr) + if err != nil { + return Info{}, fmt.Errorf("%w: %w", + ErrInvalidContext, err) + } + + if monotonicPublishedAtNs != monotonicPublishedAtNs2 { + return Info{}, ErrConcurrentUpdate + } + + if ctxErr != nil { + return Info{}, fmt.Errorf("%w: %w", ErrInvalidContext, ctxErr) + } + + return ctx, nil +} + +func IsContextMapping(isExecutable bool, mappingPath string) bool { + return !isExecutable && (mappingPath == ContextMappingMemfd || + mappingPath == ContextMappingMemfdDeleted || + mappingPath == ContextMappingAnonNamed || + mappingPath == ContextMappingMemfdNamed) +} + +func readTimestamp(rm remotememory.RemoteMemory, headerAddr libpf.Address) (uint64, error) { + var buf [8]byte + if err := rm.Read(headerAddr+monotonicPublishedAtNsOffset, buf[:]); err != nil { + return 0, fmt.Errorf("failed to read timestamp: %w", err) + } + return binary.LittleEndian.Uint64(buf[:]), nil +} + +// readHeader reads and validates the 32-byte ProcessContext header. +func readHeader(rm remotememory.RemoteMemory, headerAddr libpf.Address) (header, error) { + // Read the 32-byte header + var hdr header + if err := rm.Read(headerAddr, pfunsafe.FromPointer(&hdr)); err != nil { + return header{}, fmt.Errorf("failed to read ProcessContext header: %w", err) + } + + if pfunsafe.ToString(hdr.Signature[:]) != signatureOTELCTX { + return header{}, fmt.Errorf("invalid signature: got %q, want %q", + string(hdr.Signature[:]), signatureOTELCTX) + } + if hdr.Version != supportedVersion { + return header{}, fmt.Errorf("invalid version: got %d, want %d", + hdr.Version, supportedVersion) + } + + // Validate payload size + if hdr.PayloadSize == 0 || hdr.PayloadSize > maxPayloadSize { + return header{}, fmt.Errorf("invalid payload size: %d bytes (max %d)", + hdr.PayloadSize, maxPayloadSize) + } + + return hdr, nil +} + +func readPayload(rm remotememory.RemoteMemory, hdr header) (Info, error) { + // Read the protobuf payload from remote memory + payloadBytes := make([]byte, hdr.PayloadSize) + err := rm.Read(libpf.Address(hdr.PayloadPtr), payloadBytes) + if err != nil { + return Info{}, fmt.Errorf("failed to read payload: %w", err) + } + + // Deserialize the ProcessContext protobuf message + ctx := &processcontextpb.ProcessContext{} + if err := proto.Unmarshal(payloadBytes, ctx); err != nil { + return Info{}, fmt.Errorf("failed to unmarshal ProcessContext: %w", err) + } + + return Info{Context: ctx, PublishedAtNs: hdr.MonotonicPublishedAtNs}, nil +} + +func (p *Info) ClearExtraAttributes() { + if p.Context != nil { + p.Context.ExtraAttributes = nil + } +} diff --git a/processcontext/processcontext_test.go b/processcontext/processcontext_test.go new file mode 100644 index 000000000..0eff5fb90 --- /dev/null +++ b/processcontext/processcontext_test.go @@ -0,0 +1,374 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//go:build amd64 || arm64 + +package processcontext_test + +import ( + "encoding/binary" + "errors" + "io" + "os" + "testing" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/ebpf-profiler/libpf" + "go.opentelemetry.io/ebpf-profiler/process" + "go.opentelemetry.io/ebpf-profiler/processcontext" + processcontextpb "go.opentelemetry.io/ebpf-profiler/processcontext/v1development" + "go.opentelemetry.io/ebpf-profiler/remotememory" + commonpb "go.opentelemetry.io/proto/otlp/common/v1" + resourcepb "go.opentelemetry.io/proto/otlp/resource/v1" +) + +const ( + headerSignature = "OTEL_CTX" + supportedVersion = 2 + headerSize = 32 +) + +var testContext = processcontextpb.ProcessContext{ + Resource: &resourcepb.Resource{ + Attributes: []*commonpb.KeyValue{ + { + Key: "service.name", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_StringValue{ + StringValue: "test-service", + }, + }, + }, + }, + }, + ExtraAttributes: []*commonpb.KeyValue{ + { + Key: "custom.attribute", + Value: &commonpb.AnyValue{ + Value: &commonpb.AnyValue_StringValue{ + StringValue: "custom-value", + }, + }, + }, + }, +} + +// mockReader implements io.ReaderAt for testing. +// It stores data as a set of (address, bytes) regions and supports reads +// that span within any single stored region. +type mockReader struct { + regions []mockRegion + err error +} + +type mockRegion struct { + addr uint64 + data []byte +} + +func newMockReader() *mockReader { + return &mockReader{} +} + +func (m *mockReader) setError(err error) { + m.err = err +} + +func (m *mockReader) writeAt(addr uint64, data []byte) { + m.regions = append(m.regions, mockRegion{addr: addr, data: append([]byte{}, data...)}) +} + +func (m *mockReader) ReadAt(p []byte, off int64) (n int, err error) { + if m.err != nil { + return 0, m.err + } + + addr := uint64(off) + for _, r := range m.regions { + if addr >= r.addr && addr+uint64(len(p)) <= r.addr+uint64(len(r.data)) { + offset := addr - r.addr + copy(p, r.data[offset:offset+uint64(len(p))]) + return len(p), nil + } + } + return 0, io.EOF +} + +func createHeader(signature string, version uint32, payloadSize uint32, payloadPtr uint64, publishedAt uint64) []byte { + buf := make([]byte, headerSize) + copy(buf[0:8], []byte(signature)) + binary.LittleEndian.PutUint32(buf[8:12], version) + binary.LittleEndian.PutUint32(buf[12:16], payloadSize) + binary.LittleEndian.PutUint64(buf[16:24], publishedAt) + binary.LittleEndian.PutUint64(buf[24:32], payloadPtr) + return buf +} + +// createValidHeader creates a valid ProcessContext header +func createValidHeader(payloadSize uint32, payloadPtr uint64, publishedAt uint64) []byte { + return createHeader(headerSignature, supportedVersion, payloadSize, payloadPtr, publishedAt) +} + +func TestProcessContext_IsContextMapping(t *testing.T) { + assert.True(t, processcontext.IsContextMapping(false, "[anon:OTEL_CTX]")) + assert.True(t, processcontext.IsContextMapping(false, "[anon_shmem:OTEL_CTX]")) + assert.True(t, processcontext.IsContextMapping(false, "/memfd:OTEL_CTX")) + assert.True(t, processcontext.IsContextMapping(false, "/memfd:OTEL_CTX (deleted)")) + assert.False(t, processcontext.IsContextMapping(false, "test")) + assert.False(t, processcontext.IsContextMapping(true, "[anon:OTEL_CTX]")) +} + +func TestProcessContext_Read(t *testing.T) { + payload, err := proto.Marshal(&testContext) + require.NoError(t, err) + + mappingAddr := libpf.Address(0x1000) + + tests := []struct { + name string + setupMock func(*mockReader) + expectedResult processcontext.Info + expectedErr error + errorSubstring string + lastPublishedAtNs uint64 + }{ + { + name: "success with valid context", + setupMock: func(mock *mockReader) { + headerAddr := uint64(mappingAddr) + payloadAddr := uint64(0x2000) + header := createValidHeader(uint32(len(payload)), payloadAddr, 123456789) + mock.writeAt(headerAddr, header) + mock.writeAt(payloadAddr, payload) + }, + expectedResult: processcontext.Info{Context: &testContext, PublishedAtNs: 123456789}, + }, + { + name: "read error", + setupMock: func(mock *mockReader) { + mock.setError(errors.New("read error")) + }, + expectedErr: processcontext.ErrInvalidContext, + }, + { + name: "invalid protobuf", + setupMock: func(mock *mockReader) { + headerAddr := uint64(mappingAddr) + payloadAddr := uint64(0x2000) + invalidPayload := []byte{0xff, 0xff, 0xff, 0xff} + header := createValidHeader(uint32(len(invalidPayload)), payloadAddr, 123456789) + mock.writeAt(headerAddr, header) + mock.writeAt(payloadAddr, invalidPayload) + }, + expectedErr: processcontext.ErrInvalidContext, + errorSubstring: "failed to unmarshal", + }, + { + name: "invalid signature", + setupMock: func(mock *mockReader) { + headerAddr := uint64(mappingAddr) + header := createHeader("INVALID!", supportedVersion, 100, 0x2000, 123456789) + mock.writeAt(headerAddr, header) + }, + expectedErr: processcontext.ErrInvalidContext, + errorSubstring: "signature", + }, + { + name: "invalid version", + setupMock: func(mock *mockReader) { + headerAddr := uint64(mappingAddr) + header := createHeader(headerSignature, 999, 100, 0x2000, 123456789) + mock.writeAt(headerAddr, header) + }, + expectedErr: processcontext.ErrInvalidContext, + errorSubstring: "version", + }, + { + name: "zero payload size", + setupMock: func(mock *mockReader) { + headerAddr := uint64(mappingAddr) + header := createValidHeader(0, 0x2000, 123456789) + mock.writeAt(headerAddr, header) + }, + expectedErr: processcontext.ErrInvalidContext, + errorSubstring: "payload size", + }, + { + name: "payload size too large", + setupMock: func(mock *mockReader) { + headerAddr := uint64(mappingAddr) + header := createHeader(headerSignature, supportedVersion, 1024*1024, 0x2000, 123456789) + mock.writeAt(headerAddr, header) + }, + expectedErr: processcontext.ErrInvalidContext, + errorSubstring: "payload size", + }, + { + name: "published at zero - update in progress", + setupMock: func(mock *mockReader) { + headerAddr := uint64(mappingAddr) + // PublishedAtNs = 0 indicates update in progress + header := createValidHeader(100, 0x2000, 0) + mock.writeAt(headerAddr, header) + }, + expectedErr: processcontext.ErrConcurrentUpdate, + }, + { + name: "published at same as last published", + setupMock: func(mock *mockReader) { + headerAddr := uint64(mappingAddr) + header := createValidHeader(100, 0x2000, 123456788) + mock.writeAt(headerAddr, header) + }, + lastPublishedAtNs: 123456788, + expectedErr: processcontext.ErrNoUpdate, + }, + { + name: "published at too old", + setupMock: func(mock *mockReader) { + headerAddr := uint64(mappingAddr) + header := createValidHeader(100, 0x2000, 123456787) + mock.writeAt(headerAddr, header) + }, + lastPublishedAtNs: 123456788, + expectedErr: processcontext.ErrNoUpdate, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mock := newMockReader() + tt.setupMock(mock) + + rm := remotememory.RemoteMemory{ReaderAt: mock} + + ctx, err := processcontext.Read(mappingAddr, rm, tt.lastPublishedAtNs, 0) + + if tt.expectedErr == nil { + require.NoError(t, err) + require.NotNil(t, ctx) + require.EqualExportedValues(t, &tt.expectedResult, &ctx) + } else { + assert.Nil(t, ctx.Context) + assert.Zero(t, ctx.PublishedAtNs) + assert.Error(t, err) + assert.ErrorIs(t, err, tt.expectedErr) + if tt.errorSubstring != "" { + assert.Contains(t, err.Error(), tt.errorSubstring) + } + } + }) + } +} + +func TestProcessContext_Read_RealProcessContext(t *testing.T) { + tests := []struct { + name string + useMemfd bool + usePrctl bool + }{ + { + name: "memfd only", + useMemfd: true, + usePrctl: false, + }, + { + name: "prctl only", + useMemfd: false, + usePrctl: true, + }, + { + name: "memfd and prctl", + useMemfd: true, + usePrctl: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create test ProcessContext + payload, err := proto.Marshal(&testContext) + require.NoError(t, err) + + payloadAddr := libpf.Address(unsafe.Pointer(&payload[0])) + header := createValidHeader(uint32(len(payload)), uint64(payloadAddr), 123456789) + + memSize := len(header) + var mem []byte + if tt.useMemfd { + // Create memfd with OTEL_CTX name + fd, err := unix.MemfdCreate(headerSignature, 0) + require.NoError(t, err) + defer unix.Close(fd) + + // Set size of memfd + err = unix.Ftruncate(fd, int64(memSize)) + require.NoError(t, err) + // Map the memfd into memory + mem, err = unix.Mmap( + fd, 0, memSize, + unix.PROT_READ|unix.PROT_WRITE, + unix.MAP_PRIVATE, + ) + require.NoError(t, err) + defer unix.Munmap(mem) + unix.Close(fd) + } else { + // Create private anonymous memory mapping + mem, err = unix.Mmap( + -1, 0, memSize, + unix.PROT_READ|unix.PROT_WRITE, + unix.MAP_PRIVATE|unix.MAP_ANONYMOUS, + ) + require.NoError(t, err) + defer unix.Munmap(mem) + } + + // Write ProcessContext to memory + copy(mem[0:len(header)], header) + + if tt.usePrctl { + // Name the memory region using prctl + nameNullTerminated, _ := unix.ByteSliceFromString(headerSignature) + err = unix.Prctl(unix.PR_SET_VMA, + unix.PR_SET_VMA_ANON_NAME, + uintptr(unsafe.Pointer(&mem[0])), + uintptr(memSize), + uintptr(unsafe.Pointer(&nameNullTerminated[0])), + ) + if err != nil { + t.Skipf("prctl not supported: %v", err) + } + } + + // Get current process mappings + pid := libpf.PID(os.Getpid()) + proc := process.New(pid, pid) + defer proc.Close() + + var contextMappingAddr uint64 + _, err = proc.IterateMappings(func(m process.RawMapping) bool { + if processcontext.IsContextMapping(m.IsExecutable(), m.Path) { + contextMappingAddr = m.Vaddr + return false + } + return true + }) + if err != nil && !errors.Is(err, process.ErrCallbackStopped) { + require.NoError(t, err) + } + require.NotZero(t, contextMappingAddr) + + result, err := processcontext.Read(libpf.Address(contextMappingAddr), proc.GetRemoteMemory(), 0, 0) + require.NoError(t, err) + require.EqualExportedValues(t, + processcontext.Info{Context: &testContext, PublishedAtNs: 123456789}, + result) + + }) + } +} diff --git a/processcontext/proto/generate.sh b/processcontext/proto/generate.sh new file mode 100755 index 000000000..bd9d9ac13 --- /dev/null +++ b/processcontext/proto/generate.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +set -eu + +# This script generates Go code from the ProcessContext protobuf definition. +# It requires protoc and protoc-gen-go to be installed. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +# Create temporary directory for OpenTelemetry proto definitions +OTEL_PROTO_DIR="$(mktemp -d)" +trap "rm -rf '${OTEL_PROTO_DIR}'" EXIT + +echo "Cloning OpenTelemetry proto definitions to temporary directory..." +git clone --depth 1 --branch v1.10.0 \ + https://github.com/open-telemetry/opentelemetry-proto.git \ + "${OTEL_PROTO_DIR}" 2>/dev/null + +# Generate Go code +echo -n "Generating ProcessContext protobuf code..." +cd "${REPO_ROOT}" + +mkdir -p processcontext/v1development + +protoc \ + --go_out=processcontext/v1development \ + --go_opt=paths=source_relative \ + "--go_opt=Mprocesscontext.proto=go.opentelemetry.io/ebpf-profiler/processcontext/proto" \ + --proto_path="${OTEL_PROTO_DIR}" \ + --proto_path=processcontext/proto \ + processcontext.proto + +echo " done" diff --git a/processcontext/proto/processcontext.proto b/processcontext/proto/processcontext.proto new file mode 100644 index 000000000..e4c3b0817 --- /dev/null +++ b/processcontext/proto/processcontext.proto @@ -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. +syntax = "proto3"; +package opentelemetry.proto.processcontext.v1development; +import "opentelemetry/proto/common/v1/common.proto"; +import "opentelemetry/proto/resource/v1/resource.proto"; +option csharp_namespace = "OpenTelemetry.Proto.ProcessContext.V1Development"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.processcontext.v1development"; +option java_outer_classname = "ProcessContextProto"; +option go_package = "go.opentelemetry.io/proto/otlp/processcontext/v1development"; +// ProcessContext represents the payload for the process context sharing mechanism. +// +// This message is designed to be published by OpenTelemetry SDKs via a memory-mapped +// region, allowing external readers (such as the OpenTelemetry eBPF Profiler) to +// discover and read resource attributes from instrumented processes without requiring +// direct integration or process activity. +// +// Status: [Development] +message ProcessContext { + // The resource attributes describing this process. + // + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). The behavior of software that receives + // duplicated keys can be unpredictable. + // + // Attributes SHOULD follow OpenTelemetry semantic conventions where applicable. + // See: https://opentelemetry.io/docs/specs/semconv/ + opentelemetry.proto.resource.v1.Resource resource = 1; + // Additional attributes to share with external readers that are not part of + // the standard Resource. [Optional] + // + // This field allows publishers to include supplementary key-value pairs that + // may be useful for external readers but are not part of the SDK's configured + // Resource. + // + // Consider adding any keys here to the profiles semantic conventions in + // https://opentelemetry.io/docs/specs/semconv/general/profiles/ + repeated opentelemetry.proto.common.v1.KeyValue extra_attributes = 2; +} diff --git a/processcontext/v1development/processcontext.pb.go b/processcontext/v1development/processcontext.pb.go new file mode 100644 index 000000000..d5ff84bd1 --- /dev/null +++ b/processcontext/v1development/processcontext.pb.go @@ -0,0 +1,223 @@ +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.24.4 +// source: processcontext.proto + +package v1development + +import ( + v11 "go.opentelemetry.io/proto/otlp/common/v1" + v1 "go.opentelemetry.io/proto/otlp/resource/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// ProcessContext represents the payload for the process context sharing mechanism. +// +// This message is designed to be published by OpenTelemetry SDKs via a memory-mapped +// region, allowing external readers (such as the OpenTelemetry eBPF Profiler) to +// discover and read resource attributes from instrumented processes without requiring +// direct integration or process activity. +// +// Status: [Development] +type ProcessContext struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The resource attributes describing this process. + // + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). The behavior of software that receives + // duplicated keys can be unpredictable. + // + // Attributes SHOULD follow OpenTelemetry semantic conventions where applicable. + // See: https://opentelemetry.io/docs/specs/semconv/ + Resource *v1.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + // Additional attributes to share with external readers that are not part of + // the standard Resource. [Optional] + // + // This field allows publishers to include supplementary key-value pairs that + // may be useful for external readers but are not part of the SDK's configured + // Resource. + // + // Consider adding any keys here to the profiles semantic conventions in + // https://opentelemetry.io/docs/specs/semconv/general/profiles/ + ExtraAttributes []*v11.KeyValue `protobuf:"bytes,2,rep,name=extra_attributes,json=extraAttributes,proto3" json:"extra_attributes,omitempty"` +} + +func (x *ProcessContext) Reset() { + *x = ProcessContext{} + if protoimpl.UnsafeEnabled { + mi := &file_processcontext_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProcessContext) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProcessContext) ProtoMessage() {} + +func (x *ProcessContext) ProtoReflect() protoreflect.Message { + mi := &file_processcontext_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProcessContext.ProtoReflect.Descriptor instead. +func (*ProcessContext) Descriptor() ([]byte, []int) { + return file_processcontext_proto_rawDescGZIP(), []int{0} +} + +func (x *ProcessContext) GetResource() *v1.Resource { + if x != nil { + return x.Resource + } + return nil +} + +func (x *ProcessContext) GetExtraAttributes() []*v11.KeyValue { + if x != nil { + return x.ExtraAttributes + } + return nil +} + +var File_processcontext_proto protoreflect.FileDescriptor + +var file_processcontext_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x30, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, + 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x76, 0x31, 0x64, 0x65, 0x76, + 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x2a, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xab, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x45, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x52, + 0x0a, 0x10, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, + 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x42, 0xbc, 0x01, 0x0a, 0x33, 0x69, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, + 0x6f, 0x63, 0x65, 0x73, 0x73, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x76, 0x31, 0x64, + 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x13, 0x50, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x01, 0x5a, 0x3b, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x74, 0x6c, + 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x2f, 0x76, 0x31, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0xaa, 0x02, + 0x30, 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x2e, 0x56, 0x31, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, + 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_processcontext_proto_rawDescOnce sync.Once + file_processcontext_proto_rawDescData = file_processcontext_proto_rawDesc +) + +func file_processcontext_proto_rawDescGZIP() []byte { + file_processcontext_proto_rawDescOnce.Do(func() { + file_processcontext_proto_rawDescData = protoimpl.X.CompressGZIP(file_processcontext_proto_rawDescData) + }) + return file_processcontext_proto_rawDescData +} + +var file_processcontext_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_processcontext_proto_goTypes = []interface{}{ + (*ProcessContext)(nil), // 0: opentelemetry.proto.processcontext.v1development.ProcessContext + (*v1.Resource)(nil), // 1: opentelemetry.proto.resource.v1.Resource + (*v11.KeyValue)(nil), // 2: opentelemetry.proto.common.v1.KeyValue +} +var file_processcontext_proto_depIdxs = []int32{ + 1, // 0: opentelemetry.proto.processcontext.v1development.ProcessContext.resource:type_name -> opentelemetry.proto.resource.v1.Resource + 2, // 1: opentelemetry.proto.processcontext.v1development.ProcessContext.extra_attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_processcontext_proto_init() } +func file_processcontext_proto_init() { + if File_processcontext_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_processcontext_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProcessContext); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_processcontext_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_processcontext_proto_goTypes, + DependencyIndexes: file_processcontext_proto_depIdxs, + MessageInfos: file_processcontext_proto_msgTypes, + }.Build() + File_processcontext_proto = out.File + file_processcontext_proto_rawDesc = nil + file_processcontext_proto_goTypes = nil + file_processcontext_proto_depIdxs = nil +} diff --git a/processmanager/ebpf/ebpf.go b/processmanager/ebpf/ebpf.go index 256812cce..6b8459b7e 100644 --- a/processmanager/ebpf/ebpf.go +++ b/processmanager/ebpf/ebpf.go @@ -66,9 +66,9 @@ type ebpfMapsImpl struct { errCounterLock sync.Mutex errCounter map[metrics.MetricID]int64 - hasGenericBatchOperations bool - hasGenericBatchLookupAndDelete bool - hasLPMTrieBatchOperations bool + // Support for batch operations on LPM eBPF maps was only + // introduced with Linux kernel 5.13. + hasLPMTrieBatchOperations bool updateWorkers *asyncMapUpdaterPool } @@ -117,16 +117,6 @@ func LoadMaps(ctx context.Context, includeTracers types.IncludedTracers, impl.ExeIDToStackDeltaMaps[i-support.StackDeltaBucketSmallest] = deltasMap } - if err := probeBatchOperations(cebpf.Hash); err == nil { - log.Infof("Supports generic eBPF map batch operations") - impl.hasGenericBatchOperations = true - } - - if err := probeBatchLookupAndDelete(cebpf.Hash); err == nil { - log.Infof("Supports generic eBPF map batch lookup-and-delete") - impl.hasGenericBatchLookupAndDelete = true - } - if err := probeBatchOperations(cebpf.LPMTrie); err == nil { log.Infof("Supports LPM trie eBPF map batch operations") impl.hasLPMTrieBatchOperations = true @@ -216,7 +206,7 @@ func (impl *ebpfMapsImpl) DeleteProcData(typ libpf.InterpreterType, pid libpf.PI pid32 := uint32(pid) if err := ebpfMap.Delete(unsafe.Pointer(&pid32)); err != nil { - return fmt.Errorf("failed to remove info: %v", err) + return fmt.Errorf("failed to remove info: %w", err) } return nil } @@ -449,6 +439,12 @@ func probeBatchOperations(mapType cebpf.MapType) error { return probeMapOperations(mapType, probeBatchOperationsInner[uint64]) } +// SupportsLPMTrieBatchOperations returns true if the kernel supports eBPF batch operations +// on LPM trie maps. +func (impl *ebpfMapsImpl) SupportsLPMTrieBatchOperations() bool { + return impl.hasLPMTrieBatchOperations +} + // getMapID returns the mapID number to use for given number of stack deltas. func getMapID(numDeltas uint32) (uint16, error) { significantBits := 32 - bits.LeadingZeros32(numDeltas) @@ -538,44 +534,26 @@ func (impl *ebpfMapsImpl) UpdateExeIDToStackDeltas(fileID host.FileID, impl.updateWorkers.EnqueueUpdate(outerMap, fileID, innerMapCloned) - if impl.hasGenericBatchOperations { - innerKeys := make([]uint32, numDeltas) - stackDeltas := make([]support.StackDelta, numDeltas) - - // Prepare values for batch update. - for index, delta := range deltas { - innerKeys[index] = uint32(index) - stackDeltas[index].AddrLow = delta.AddressLow - stackDeltas[index].UnwindInfo = delta.UnwindInfo - } + innerKeys := make([]uint32, numDeltas) + stackDeltas := make([]support.StackDelta, numDeltas) - _, err := innerMap.BatchUpdate( - ptrCastMarshaler[uint32](innerKeys), - ptrCastMarshaler[support.StackDelta](stackDeltas), - &cebpf.BatchOptions{Flags: uint64(cebpf.UpdateAny)}) - if err != nil { - return 0, impl.trackMapError(metrics.IDExeIDToStackDeltasBatchUpdate, - fmt.Errorf("failed to batch insert %d elements for 0x%x "+ - "into exeIDTostack_deltas: %v", - numDeltas, fileID, err)) - } - return mapID, nil - } - - innerKey := uint32(0) - stackDelta := support.StackDelta{} + // Prepare values for batch update. for index, delta := range deltas { - stackDelta.AddrLow = delta.AddressLow - stackDelta.UnwindInfo = delta.UnwindInfo - innerKey = uint32(index) - if err := innerMap.Update(unsafe.Pointer(&innerKey), unsafe.Pointer(&stackDelta), - cebpf.UpdateAny); err != nil { - return 0, impl.trackMapError(metrics.IDExeIDToStackDeltasUpdate, fmt.Errorf( - "failed to insert element %d for 0x%x into exeIDTostack_deltas: %v", - index, fileID, err)) - } + innerKeys[index] = uint32(index) + stackDeltas[index].AddrLow = delta.AddressLow + stackDeltas[index].UnwindInfo = delta.UnwindInfo } + _, err = innerMap.BatchUpdate( + ptrCastMarshaler[uint32](innerKeys), + ptrCastMarshaler[support.StackDelta](stackDeltas), + &cebpf.BatchOptions{Flags: uint64(cebpf.UpdateAny)}) + if err != nil { + return 0, impl.trackMapError(metrics.IDExeIDToStackDeltasBatchUpdate, + fmt.Errorf("failed to batch insert %d elements for 0x%x "+ + "into exeIDTostack_deltas: %v", + numDeltas, fileID, err)) + } return mapID, nil } @@ -617,22 +595,12 @@ func (impl *ebpfMapsImpl) UpdateStackDeltaPages(fileID host.FileID, numDeltasPer firstDelta += uint32(numDeltas) } - if impl.hasGenericBatchOperations { - _, err := impl.StackDeltaPageToInfo.BatchUpdate( - ptrCastMarshaler[support.StackDeltaPageKey](keys), - ptrCastMarshaler[support.StackDeltaPageInfo](values), - &cebpf.BatchOptions{Flags: uint64(cebpf.UpdateNoExist)}) - return impl.trackMapError(metrics.IDStackDeltaPageToInfoBatchUpdate, err) - } + _, err := impl.StackDeltaPageToInfo.BatchUpdate( + ptrCastMarshaler[support.StackDeltaPageKey](keys), + ptrCastMarshaler[support.StackDeltaPageInfo](values), + &cebpf.BatchOptions{Flags: uint64(cebpf.UpdateNoExist)}) + return impl.trackMapError(metrics.IDStackDeltaPageToInfoBatchUpdate, err) - for index := range keys { - if err := impl.trackMapError(metrics.IDStackDeltaPageToInfoUpdate, - impl.StackDeltaPageToInfo.Update(unsafe.Pointer(&keys[index]), - unsafe.Pointer(&values[index]), cebpf.UpdateNoExist)); err != nil { - return err - } - } - return nil } // DeleteStackDeltaPage removes the entry specified by fileID and page from the eBPF map. @@ -672,7 +640,7 @@ func (impl *ebpfMapsImpl) UpdatePidPageMappingInfo(pid libpf.PID, prefix lpm.Pre // DeletePidPageMappingInfo removes the elements specified by prefixes from eBPF map // pid_page_to_mapping_info and returns the number of elements removed. -func (impl *ebpfMapsImpl) DeletePidPageMappingInfo(pid libpf.PID, prefixes []lpm.Prefix) (int, +func (impl *ebpfMapsImpl) DeletePidPageMappingInfo(pid libpf.PID, prefixes []lpm.Prefix) (uint64, error, ) { if impl.hasLPMTrieBatchOperations { @@ -688,11 +656,11 @@ func (impl *ebpfMapsImpl) DeletePidPageMappingInfo(pid libpf.PID, prefixes []lpm return impl.DeletePidPageMappingInfoSingle(pid, prefixes) } -func (impl *ebpfMapsImpl) DeletePidPageMappingInfoSingle(pid libpf.PID, prefixes []lpm.Prefix) (int, +func (impl *ebpfMapsImpl) DeletePidPageMappingInfoSingle(pid libpf.PID, prefixes []lpm.Prefix) (uint64, error, ) { cKey := &support.PIDPage{} - var deleted int + var deleted uint64 var combinedErrors error for _, prefix := range prefixes { *cKey = getPIDPageFromPrefix(pid, prefix) @@ -706,7 +674,7 @@ func (impl *ebpfMapsImpl) DeletePidPageMappingInfoSingle(pid libpf.PID, prefixes return deleted, combinedErrors } -func (impl *ebpfMapsImpl) DeletePidPageMappingInfoBatch(pid libpf.PID, prefixes []lpm.Prefix) (int, +func (impl *ebpfMapsImpl) DeletePidPageMappingInfoBatch(pid libpf.PID, prefixes []lpm.Prefix) (uint64, error, ) { // Prepare all keys based on the given prefixes. @@ -717,7 +685,13 @@ func (impl *ebpfMapsImpl) DeletePidPageMappingInfoBatch(pid libpf.PID, prefixes deleted, err := impl.PidPageToMappingInfo.BatchDelete( ptrCastMarshaler[support.PIDPage](cKeys), nil) - return deleted, impl.trackMapError(metrics.IDPidPageToMappingInfoBatchDelete, err) + + // BatchDelete returns a count of deleted entries, so this should never happen. + if deleted < 0 { + err = errors.Join(err, fmt.Errorf("negative batch delete count: %d", deleted)) + deleted = 0 + } + return uint64(deleted), impl.trackMapError(metrics.IDPidPageToMappingInfoBatchDelete, err) } // LookupPidPageInformation returns the fileID and bias for a given pid and page combination from @@ -737,24 +711,6 @@ func (impl *ebpfMapsImpl) LookupPidPageInformation(pid libpf.PID, page uint64) ( return host.FileID(cValue.File_id), bias, nil } -// SupportsGenericBatchOperations returns true if the kernel supports eBPF batch operations -// on hash and array maps. -func (impl *ebpfMapsImpl) SupportsGenericBatchOperations() bool { - return impl.hasGenericBatchOperations -} - -// SupportsGenericBatchLookupAndDelete returns true if the kernel supports eBPF batch -// lookup-and-delete operations on hash and array maps. -func (impl *ebpfMapsImpl) SupportsGenericBatchLookupAndDelete() bool { - return impl.hasGenericBatchLookupAndDelete -} - -// SupportsLPMTrieBatchOperations returns true if the kernel supports eBPF batch operations -// on LPM trie maps. -func (impl *ebpfMapsImpl) SupportsLPMTrieBatchOperations() bool { - return impl.hasLPMTrieBatchOperations -} - // ptrCastMarshaler is a small wrapper type intended to be used with cilium's BatchUpdate and // BackDelete functions. // diff --git a/processmanager/ebpf/ebpf_integration_test.go b/processmanager/ebpf/ebpf_integration_test.go index 8d7915dbc..59a7f7861 100644 --- a/processmanager/ebpf/ebpf_integration_test.go +++ b/processmanager/ebpf/ebpf_integration_test.go @@ -15,6 +15,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/lpm" + "go.opentelemetry.io/ebpf-profiler/metrics" "go.opentelemetry.io/ebpf-profiler/rlimit" "go.opentelemetry.io/ebpf-profiler/support" ) @@ -34,6 +35,7 @@ func loadTracers(t *testing.T) *ebpfMapsImpl { return &ebpfMapsImpl{ PidPageToMappingInfo: pidPageToMappingInfo, + errCounter: make(map[metrics.MetricID]int64), } } diff --git a/processmanager/ebpfapi/ebpf.go b/processmanager/ebpfapi/ebpf.go index a750d2fef..aa244f728 100644 --- a/processmanager/ebpfapi/ebpf.go +++ b/processmanager/ebpfapi/ebpf.go @@ -51,19 +51,11 @@ type EbpfHandler interface { // DeletePidPageMappingInfo removes the elements specified by prefixes from eBPF map // pid_page_to_mapping_info and returns the number of elements removed. - DeletePidPageMappingInfo(pid libpf.PID, prefixes []lpm.Prefix) (int, error) + DeletePidPageMappingInfo(pid libpf.PID, prefixes []lpm.Prefix) (uint64, error) // CollectMetrics returns gathered errors for changes to eBPF maps. CollectMetrics() []metrics.Metric - // SupportsGenericBatchOperations returns true if the kernel supports eBPF batch operations - // on hash and array maps. - SupportsGenericBatchOperations() bool - - // SupportsGenericBatchLookupAndDelete returns true if the kernel supports eBPF batch - // lookup-and-delete operations on hash and array maps. - SupportsGenericBatchLookupAndDelete() bool - // SupportsLPMTrieBatchOperations returns true if the kernel supports eBPF batch operations // on LPM trie maps. SupportsLPMTrieBatchOperations() bool diff --git a/processmanager/execinfomanager/synthdeltas.go b/processmanager/execinfomanager/synthdeltas.go index 29b384bbb..bf42045a8 100644 --- a/processmanager/execinfomanager/synthdeltas.go +++ b/processmanager/execinfomanager/synthdeltas.go @@ -48,14 +48,14 @@ func createVDSOSyntheticRecordArm64(ef *pfelf.File) sdtypes.IntervalData { if sym.Name == "__kernel_rt_sigreturn" { deltas = append( deltas, - sdtypes.StackDelta{Address: addr, Info: sdtypes.UnwindInfoSignal}, + sdtypes.StackDelta{Address: addr - 1, Info: sdtypes.UnwindInfoSignal}, sdtypes.StackDelta{Address: addr + sym.Size, Info: sdtypes.UnwindInfoLR}, ) return true } // Determine if LR is on stack code := make([]byte, sym.Size) - if _, err := ef.ReadVirtualMemory(code, int64(sym.Address)); err != nil { + if _, err := ef.ReadAt(code, int64(sym.Address)); err != nil { return true } diff --git a/processmanager/execinfomanager/synthdeltas_test.go b/processmanager/execinfomanager/synthdeltas_test.go index d16c65c1d..272498986 100644 --- a/processmanager/execinfomanager/synthdeltas_test.go +++ b/processmanager/execinfomanager/synthdeltas_test.go @@ -28,7 +28,7 @@ func TestVDSOArm64(t *testing.T) { {Address: 0x7e4, Info: sdtypes.UnwindInfoLR}, {Address: 0x800, Info: frameSize16}, {Address: 0x80c, Info: sdtypes.UnwindInfoLR}, - {Address: 0x8f8, Info: sdtypes.UnwindInfoSignal}, + {Address: 0x8f7, Info: sdtypes.UnwindInfoSignal}, {Address: 0x900, Info: sdtypes.UnwindInfoLR}, }, } diff --git a/processmanager/manager.go b/processmanager/manager.go index 0148d99b6..eddc60f26 100644 --- a/processmanager/manager.go +++ b/processmanager/manager.go @@ -24,6 +24,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/metrics" "go.opentelemetry.io/ebpf-profiler/nativeunwind" "go.opentelemetry.io/ebpf-profiler/periodiccaller" + "go.opentelemetry.io/ebpf-profiler/process" pmebpf "go.opentelemetry.io/ebpf-profiler/processmanager/ebpfapi" eim "go.opentelemetry.io/ebpf-profiler/processmanager/execinfomanager" "go.opentelemetry.io/ebpf-profiler/reporter" @@ -94,6 +95,11 @@ func New(ctx context.Context, includeTracers types.IncludedTracers, monitorInter interpreters := make(map[libpf.PID]map[util.OnDiskFileIdentifier]interpreter.Instance) + selfContainerID, selfCgroupIno, err := process.DetectSelfContainerIDViaInode() + if err != nil { + log.Debugf("Failed to detect self container ID via inode: %v", err) + } + pm := &ProcessManager{ interpreterTracerEnabled: em.NumInterpreterLoaders() > 0, eim: em, @@ -108,6 +114,8 @@ func New(ctx context.Context, includeTracers types.IncludedTracers, monitorInter metricsAddSlice: metrics.AddSlice, filterErrorFrames: filterErrorFrames, includeEnvVars: includeEnvVars, + selfCgroupIno: selfCgroupIno, + selfContainerID: selfContainerID, } collectInterpreterMetrics(ctx, pm, monitorInterval) @@ -317,8 +325,10 @@ func (pm *ProcessManager) HandleTrace(bpfTrace *libpf.EbpfTrace) { ExecutablePath: bpfTrace.ExecutablePath, ContainerID: bpfTrace.ContainerID, Origin: bpfTrace.Origin, - OffTime: bpfTrace.OffTime, + Value: bpfTrace.Value, EnvVars: bpfTrace.EnvVars, + TraceID: bpfTrace.APMTraceID, + SpanID: bpfTrace.APMTransactionID, } pid := bpfTrace.PID diff --git a/processmanager/manager_test.go b/processmanager/manager_test.go new file mode 100644 index 000000000..0be3c7c19 --- /dev/null +++ b/processmanager/manager_test.go @@ -0,0 +1,157 @@ +package processmanager + +import ( + "os" + "runtime" + "slices" + "testing" + + lru "github.com/elastic/go-freelru" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/ebpf-profiler/host" + "go.opentelemetry.io/ebpf-profiler/interpreter" + golang "go.opentelemetry.io/ebpf-profiler/interpreter/go" + "go.opentelemetry.io/ebpf-profiler/libpf" + "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" + "go.opentelemetry.io/ebpf-profiler/process" + "go.opentelemetry.io/ebpf-profiler/remotememory" + "go.opentelemetry.io/ebpf-profiler/reporter/samples" + "go.opentelemetry.io/ebpf-profiler/util" +) + +type traceCapture struct { + traces []*libpf.Trace +} + +func (tc *traceCapture) ReportTraceEvent(trace *libpf.Trace, _ *samples.TraceEventMeta) error { + tc.traces = append(tc.traces, trace) + return nil +} + +func TestFrameCacheCrossProcessPollution(t *testing.T) { + require := require.New(t) + + exec, err := os.Executable() + require.NoError(err) + + pc, _, _, ok := runtime.Caller(0) + require.True(ok) + + goPID := libpf.PID(1000) + catPID := libpf.PID(2000) + + goHostFileID, err := host.FileIDFromBytes( + []byte{0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55}) + require.NoError(err) + catHostFileID, err := host.FileIDFromBytes( + []byte{0xCA, 0x7C, 0xA7, 0xCA, 0x7C, 0xA7, 0xCA, 0x7C}) + require.NoError(err) + libcHostFileID, err := host.FileIDFromBytes( + []byte{0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE}) + require.NoError(err) + + realPID := libpf.PID(os.Getpid()) + pid := process.New(realPID, realPID) + elfRef := pfelf.NewReference(exec, pid) + loaderInfo := interpreter.NewLoaderInfo(goHostFileID, elfRef) + rm := remotememory.NewProcessVirtualMemory(realPID) + + goData, err := golang.Loader(nil, loaderInfo) + require.NoError(err) + goInstance, err := goData.Attach(nil, realPID, 0x0, rm) + require.NoError(err) + + goODID := util.OnDiskFileIdentifier{DeviceID: 1, InodeNum: 1} + + frameCache, err := lru.New[frameCacheKey, libpf.Frames](1024, hashFrameCacheKey) + require.NoError(err) + frameCache.SetLifetime(frameCacheLifetime) + + goMappings := []Mapping{ + {FrameMapping: libpf.NewFrameMapping(libpf.FrameMappingData{ + File: libpf.NewFrameMappingFile(libpf.FrameMappingFileData{ + FileID: libpf.NewFileID(uint64(goHostFileID), 0), + FileName: libpf.Intern("go-binary"), + }), + Start: 0, + End: 0xFFFFFFF, + })}, + {FrameMapping: libpf.NewFrameMapping(libpf.FrameMappingData{ + File: libpf.NewFrameMappingFile(libpf.FrameMappingFileData{ + FileID: libpf.NewFileID(uint64(libcHostFileID), 0), + FileName: libpf.Intern("libc.so.6"), + }), + Start: 0, + End: 0xFFFFFFF, + })}, + } + slices.SortFunc(goMappings, compareMapping) + + catMappings := []Mapping{ + {FrameMapping: libpf.NewFrameMapping(libpf.FrameMappingData{ + File: libpf.NewFrameMappingFile(libpf.FrameMappingFileData{ + FileID: libpf.NewFileID(uint64(catHostFileID), 0), + FileName: libpf.Intern("cat"), + }), + Start: 0, + End: 0xFFFFFFF, + })}, + {FrameMapping: libpf.NewFrameMapping(libpf.FrameMappingData{ + File: libpf.NewFrameMappingFile(libpf.FrameMappingFileData{ + FileID: libpf.NewFileID(uint64(libcHostFileID), 0), + FileName: libpf.Intern("libc.so.6"), + }), + Start: 0, + End: 0xFFFFFFF, + })}, + } + slices.SortFunc(catMappings, compareMapping) + + capture := &traceCapture{} + pm := &ProcessManager{ + interpreters: map[libpf.PID]map[util.OnDiskFileIdentifier]interpreter.Instance{ + goPID: {goODID: goInstance}, + }, + pidToProcessInfo: map[libpf.PID]*processInfo{ + goPID: {mappings: goMappings}, + catPID: {mappings: catMappings}, + }, + frameCache: frameCache, + traceReporter: capture, + } + + libcFrame := libpf.NewEbpfFrame(libpf.NativeFrame, 0, 2, uint64(pc)) + libcFrame[1] = uint64(libcHostFileID) + + pm.HandleTrace(&libpf.EbpfTrace{ + PID: goPID, + TID: goPID, + NumFrames: 1, + FrameData: libcFrame, + }) + + require.Len(capture.traces, 1) + goTrace := capture.traces[0] + require.NotEmpty(goTrace.Frames) + + goFrame := goTrace.Frames[0].Value() + assert.Equal(t, libpf.NativeFrame, goFrame.Type) + assert.Equal(t, "", goFrame.FunctionName.String()) + + pm.HandleTrace(&libpf.EbpfTrace{ + PID: catPID, + TID: catPID, + NumFrames: 1, + FrameData: libcFrame, + }) + + require.Len(capture.traces, 2) + catTrace := capture.traces[1] + require.NotEmpty(catTrace.Frames) + + catFrame := catTrace.Frames[0].Value() + assert.Equal(t, libpf.NativeFrame, catFrame.Type) + assert.Equal(t, "", catFrame.FunctionName.String()) +} diff --git a/processmanager/processinfo.go b/processmanager/processinfo.go index f1efde54c..855717e86 100644 --- a/processmanager/processinfo.go +++ b/processmanager/processinfo.go @@ -18,6 +18,7 @@ import ( "path" "slices" "sort" + "strings" "syscall" "time" @@ -31,6 +32,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/lpm" "go.opentelemetry.io/ebpf-profiler/process" + "go.opentelemetry.io/ebpf-profiler/processcontext" "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/times" "go.opentelemetry.io/ebpf-profiler/util" @@ -127,8 +129,10 @@ func (pm *ProcessManager) getPidInformation(pid libpf.PID, pr process.Process, return nil } + meta := pr.GetProcessMeta(process.MetaConfig{IncludeEnvVars: pm.includeEnvVars}) + pm.fillSelfContainerID(pid, &meta) info := &processInfo{ - meta: pr.GetProcessMeta(process.MetaConfig{IncludeEnvVars: pm.includeEnvVars}), + meta: meta, libcInfo: nil, } pm.pidToProcessInfo[pid] = info @@ -136,6 +140,23 @@ func (pm *ProcessManager) getPidInformation(pid libpf.PID, pr process.Process, return info } +// fillSelfContainerID sets the container ID on meta if the process has the same cgroup +// directory root as the profiler and the standard cgroup-based detection returned no result. +func (pm *ProcessManager) fillSelfContainerID(pid libpf.PID, meta *process.ProcessMeta) { + if meta.ContainerID != libpf.NullString || pm.selfContainerID == libpf.NullString { + return + } + ino, err := process.CgroupRootInode(pid) + if err != nil { + return + } + if ino == pm.selfCgroupIno { + meta.ContainerID = pm.selfContainerID + } else { + log.Debugf("Process %d cgroup inode (%d) doesn't match profiler (%d)", pid, ino, pm.selfCgroupIno) + } +} + // assignInterpreter will update the interpreters maps with given interpreter.Instance. // Caller is responsible to hold pm.mu write lock to avoid race conditions. func (pm *ProcessManager) assignInterpreter(pid libpf.PID, key util.OnDiskFileIdentifier, @@ -189,7 +210,7 @@ func (pm *ProcessManager) handleNewInterpreter(pr process.Process, bias libpf.Ad return nil } -func (pm *ProcessManager) getELFInfo(pr process.Process, mapping *process.Mapping, +func (pm *ProcessManager) getELFInfo(pr process.Process, mapping *process.RawMapping, elfRef *pfelf.Reference, ) elfInfo { key := mapping.GetOnDiskFileIdentifier() @@ -224,7 +245,7 @@ func (pm *ProcessManager) getELFInfo(pr process.Process, mapping *process.Mappin return info } - baseName := path.Base(mapping.Path.String()) + baseName := path.Base(mapping.Path) if baseName == "/" { // There are circumstances where there is no filename. // E.g. kernel module 'bpfilter_umh' before Linux 5.9-rc1 uses @@ -297,7 +318,7 @@ func (pm *ProcessManager) processRemovedMapping(pid libpf.PID, m *Mapping) uint6 fileID := host.FileIDFromLibpf(mf.File.Value().FileID) pm.eim.DecRef(fileID) - return uint64(deleted) + return deleted } // Caller is responsible to hold pm.mu write lock to avoid race conditions. @@ -331,8 +352,8 @@ func (pm *ProcessManager) processRemovedInterpreters(pid libpf.PID, var errInvalidVirtualAddress = errors.New("invalid ELF virtual address") -func (pm *ProcessManager) newFrameMapping(pr process.Process, m *process.Mapping) (libpf.FrameMapping, error) { - elfRef := pfelf.NewReference(m.Path.String(), pr) +func (pm *ProcessManager) newFrameMapping(pr process.Process, m *process.RawMapping) (libpf.FrameMapping, error) { + elfRef := pfelf.NewReference(m.Path, pr) defer elfRef.Close() info := pm.getELFInfo(pr, m, elfRef) @@ -349,7 +370,7 @@ func (pm *ProcessManager) newFrameMapping(pr process.Process, m *process.Mapping elfSpaceVA, ok := info.addressMapper.FileOffsetToVirtualAddress(m.FileOffset) if !ok { - log.Debugf("Failed to map file offset of PID %d, file %s, offset %d", + log.Warnf("Failed to map file offset of PID %d, file %s, offset %d", pr.PID(), m.Path, m.FileOffset) return libpf.FrameMapping{}, errInvalidVirtualAddress } @@ -357,6 +378,8 @@ func (pm *ProcessManager) newFrameMapping(pr process.Process, m *process.Mapping fileID := host.FileIDFromLibpf(info.mappingFile.Value().FileID) ei, err := pm.eim.AddOrIncRef(fileID, elfRef) if err != nil { + log.Errorf("Failed to load executable info for PID %d file %v (fileID %s): %v", + pr.PID(), m.Path, fileID.StringNoQuotes(), err) return libpf.FrameMapping{}, err } @@ -394,37 +417,97 @@ func compareMapping(a, b Mapping) int { return 0 } -// synchronizeMappings synchronizes executable mappings for the given PID. -// This method will be called when a PID is first encountered or when the eBPF -// code encounters an address in an executable mapping that HA has no information -// on. Therefore, executable mapping synchronization takes place lazily on-demand, -// and map/unmap operations are not precisely tracked (reduce processing load). -// This means that at any point, we may have cached stale (or miss) executable -// mappings. The expectation is that stale mappings will disappear and new -// mappings cached at the next synchronization triggered by process exit or -// unknown address encountered. +// processPIDExit informs the ProcessManager that a process exited and no longer will be scheduled. +// exitKTime is stored for later processing in ProcessedUntil, when traces up to this time have been +// processed. There can be a race condition if we can not clean up the references for this process +// fast enough and this particular pid is reused again by the system. +func (pm *ProcessManager) processPIDExit(pid libpf.PID) { + exitKTime := times.GetKTime() + log.Debugf("- PID: %v", pid) + + var err error + defer func() { + if err != nil { + log.Error(err) + } + }() + defer pm.ebpf.RemoveReportedPID(pid) + pm.mu.Lock() + defer pm.mu.Unlock() + + info, pidExists := pm.pidToProcessInfo[pid] + if !pidExists { + log.Debugf("Skip process exit handling for unknown PID %d", pid) + return + } + + // processPIDExit may be called multiple times in short succession + // for the same PID, don't update exitKTime if we've previously recorded it. + if _, pidExitProcessed := pm.exitEvents[pid]; !pidExitProcessed { + pm.exitEvents[pid] = exitKTime + } else { + log.Debugf("Skip duplicate process exit handling for PID %d", pid) + return + } + + // Delete all entries we have for this particular PID from pid_page_to_mapping_info. + deleted, err2 := pm.ebpf.DeletePidPageMappingInfo(pid, []lpm.Prefix{dummyPrefix}) + if err2 != nil { + err = errors.Join(err, fmt.Errorf("failed to delete dummy prefix for PID %d: %v", + pid, err2)) + } + + for idx := range info.mappings { + deleted += pm.processRemovedMapping(pid, &info.mappings[idx]) + } + pm.pidPageToMappingInfoSize -= min(pm.pidPageToMappingInfoSize, deleted) + pm.processRemovedInterpreters(pid, libpf.Set[util.OnDiskFileIdentifier]{}) +} + +// SynchronizeProcess triggers ProcessManager to update its internal information +// about a process. It synchronizes executable mappings for the given PID by +// parsing /proc/PID/maps and building the internal mapping state directly in +// a single pass. This method will be called when a PID is first encountered or +// when the eBPF code encounters an address in an executable mapping that HA has +// no information on. Therefore, executable mapping synchronization takes place +// lazily on-demand, and map/unmap operations are not precisely tracked (reduce +// processing load). This means that at any point, we may have cached stale (or +// miss) executable mappings. The expectation is that stale mappings will +// disappear and new mappings cached at the next synchronization triggered by +// process exit or unknown address encountered. // // TODO: Periodic synchronization of mappings for every tracked PID. -func (pm *ProcessManager) synchronizeMappings(pr process.Process, - processMappings []process.Mapping) bool { +func (pm *ProcessManager) SynchronizeProcess(pr process.Process) { pid := pr.PID() + log.Debugf("= PID: %v", pid) + + // Abort early if process is waiting for cleanup in ProcessedUntil + pm.mu.Lock() + _, ok := pm.exitEvents[pid] + pm.mu.Unlock() + + if ok { + log.Debugf("PID %v waiting for cleanup, aborting SynchronizeProcess", pid) + pm.ebpf.RemoveReportedPID(pid) + return + } // Get current executable name - exe, err := pr.GetExe() - if err != nil && !os.IsNotExist(err) { + exe, exeErr := pr.GetExe() + if exeErr != nil && !os.IsNotExist(exeErr) { // The /proc/PID/exe returns "not exists" error also in // the case of main thread exit. Ignore it. - log.Warnf("Failed to get executable of process %d: %v", pid, err) } pm.mu.Lock() info := pm.getPidInformation(pid, pr) if info == nil { pm.mu.Unlock() - return false + return } // Check if process meta needs an update updateProcessMeta := exe != libpf.NullString && exe != info.meta.Executable + oldProcessContextInfo := info.meta.ProcessContextInfo // Get existing info oldMappings := info.mappings @@ -442,56 +525,133 @@ func (pm *ProcessManager) synchronizeMappings(pr process.Process, mpRemove[uint64(m.Vaddr)] = m } - // Generate the list of new processmanager mappings and interpreters. - // Reuse existing mappings if possible. - mappings := make([]Mapping, 0, len(processMappings)) - mpAdd := make([]*Mapping, 0, len(processMappings)) + // interpreterMappings collects the subset of mappings relevant to interpreters: + // executable anonymous mappings (JIT) and DLL file-backed mappings (.NET PE). + // They are in /proc/PID/maps order (ascending Vaddr), not sorted otherwise. + interpreterMappings := make([]process.RawMapping, 0, 8) interpretersValid := make(libpf.Set[util.OnDiskFileIdentifier], numInterpreters) - for idx := range processMappings { - m := &processMappings[idx] - if !m.IsExecutable() || m.IsAnonymous() { - continue + capHint := max(32, min(len(oldMappings), 256)) + mappings := make([]Mapping, 0, capHint) + mpAdd := make([]*Mapping, 0, capHint) + var processContextInfo processcontext.Info + + pm.mappingStats.numProcAttempts.Add(1) + start := time.Now() + + // This callback processes each memory mapping, keeping only executable + // file-backed mappings and anonymous executable/DLL mappings needed by interpreters. + // All other mappings are skipped. + numParseErrors, err := pr.IterateMappings(func(m process.RawMapping) bool { + if processcontext.IsContextMapping(m.IsExecutable(), m.Path) { + processContextInfo = readProcessContext(m.Vaddr, pr, oldProcessContextInfo) + // Even if process context is not found, it might be published in the future. + // For now, we rely on a new call to synchronizeMappings to pick it up. + // TODO: Add some kind of polling mechanism or a hook on prctl to be notified + // when the process context is published. } - var fm libpf.FrameMapping - if oldm, ok := mpRemove[m.Vaddr]; ok { - if oldm.Length == m.Length && oldm.Device == m.Device && oldm.Inode == m.Inode { - delete(mpRemove, m.Vaddr) - fm = oldm.FrameMapping - } + // Executable mappings and VDSO, converted directly to libpf.FrameMapping + mappingNeeded := m.IsExecutable() && !m.IsAnonymous() + // Needed for JIT mappings (Hotspot, V8, BEAM, etc.) + interpreterNeeded := m.IsExecutable() && m.IsAnonymous() + // Needed by .NET to retrieve PE assembly mappings + interpreterNeeded = interpreterNeeded || strings.HasSuffix(m.Path, ".dll") + if !mappingNeeded && !interpreterNeeded { + return true } - newMapping := false - if !fm.Valid() { - newMapping = true - var err error - fm, err = pm.newFrameMapping(pr, m) - if err != nil { - // newFrameMapping logged the message if needed - continue + + m.Path = libpf.Intern(m.Path).String() + + if mappingNeeded { + var fm libpf.FrameMapping + if oldm, ok := mpRemove[m.Vaddr]; ok { + if oldm.Length == m.Length && oldm.Device == m.Device && oldm.Inode == m.Inode { + delete(mpRemove, m.Vaddr) + fm = oldm.FrameMapping + } + } + newMapping := false + if !fm.Valid() { + newMapping = true + // Error is expected for non-ELF files (e.g. PE DLL); + // fm will be invalid and the mapping skipped below but will enter the interpreter mappings block. + fm, _ = pm.newFrameMapping(pr, &m) + } + if fm.Valid() { + key := m.GetOnDiskFileIdentifier() + interpretersValid[key] = libpf.Void{} + + mappings = append(mappings, Mapping{ + Vaddr: libpf.Address(m.Vaddr), + Length: m.Length, + Device: m.Device, + Inode: m.Inode, + FrameMapping: fm, + }) + if newMapping { + mpAdd = append(mpAdd, &mappings[len(mappings)-1]) + } } } - key := m.GetOnDiskFileIdentifier() - interpretersValid[key] = libpf.Void{} - - mappings = append(mappings, Mapping{ - Vaddr: libpf.Address(m.Vaddr), - Length: m.Length, - Device: m.Device, - Inode: m.Inode, - FrameMapping: fm, - }) - if newMapping { - mpAdd = append(mpAdd, &mappings[len(mappings)-1]) + if interpreterNeeded { + interpreterMappings = append(interpreterMappings, m) + } + return true + }) + + elapsed := time.Since(start) + pm.mappingStats.numProcParseErrors.Add(numParseErrors) + + if err != nil { + switch { + case errors.Is(err, process.ErrCallbackStopped): + // Defensive: the current callback does not stop early, but the + // IterateMappings contract allows it. Treat as non-fatal and + // continue with whatever mappings were collected so far. + err = nil + case os.IsPermission(err): + // Ignore the synchronization completely in case of permission + // error. This implies the process is still alive, but we cannot + // inspect it. Exiting here keeps the PID in the eBPF maps so + // we avoid a notification flood to resynchronize. + pm.mappingStats.errProcPerm.Add(1) + return + case errors.Is(err, process.ErrNoMappings): + // When no mappings can be extracted but the process is still alive, + // do not trigger a process exit to avoid unloading process metadata. + // As it's likely that a future iteration can extract mappings from a + // different thread in the process, notify eBPF to enable further notifications. + pm.ebpf.RemoveReportedPID(pid) + return + case os.IsNotExist(err): + // Since listing /proc and opening files in there later is inherently racy, + // we expect to lose the race sometimes and thus expect to hit os.IsNotExist. + pm.mappingStats.errProcNotExist.Add(1) + log.Debugf("removing pid due to mappings read error: %v", err) + pm.processPIDExit(pid) + return + default: + if e, ok := err.(*os.PathError); ok && e.Err == syscall.ESRCH { + // If the process exits while reading its /proc/$PID/maps, the kernel will + // return ESRCH. Handle it as if the process did not exist. + pm.mappingStats.errProcESRCH.Add(1) + } + log.Debugf("removing pid due to mappings read error: %v", err) + pm.processPIDExit(pid) + return } } + util.AtomicUpdateMaxUint32(&pm.mappingStats.maxProcParseUsec, uint32(elapsed.Microseconds())) + pm.mappingStats.totalProcParseUsec.Add(uint32(elapsed.Microseconds())) + // Detach removed interpreters and remove old mappings numChanges := uint64(0) for _, m := range mpRemove { numChanges += pm.processRemovedMapping(pid, m) } - pm.pidPageToMappingInfoSize -= numChanges + pm.pidPageToMappingInfoSize -= min(pm.pidPageToMappingInfoSize, numChanges) pm.mu.Lock() pm.processRemovedInterpreters(pid, interpretersValid) pm.mu.Unlock() @@ -507,6 +667,7 @@ func (pm *ProcessManager) synchronizeMappings(pr process.Process, var meta process.ProcessMeta if updateProcessMeta { meta = pr.GetProcessMeta(process.MetaConfig{IncludeEnvVars: pm.includeEnvVars}) + pm.fillSelfContainerID(pid, &meta) } // Sort and publish the new mappings and meta @@ -518,13 +679,14 @@ func (pm *ProcessManager) synchronizeMappings(pr process.Process, if updateProcessMeta { info.meta = meta } + info.meta.ProcessContextInfo = processContextInfo } interpreters := pm.interpreters[pid] pm.mu.Unlock() // Synchronize all interpreters with updated mappings for _, instance := range interpreters { - err := instance.SynchronizeMappings(pm.ebpf, pm.exeReporter, pr, processMappings) + err := instance.SynchronizeMappings(pm.ebpf, pm.exeReporter, pr, interpreterMappings) if err != nil { if alive, _ := isPIDLive(pid); alive { log.Errorf("Failed to handle new anonymous mapping for PID %d: %v", pid, err) @@ -539,118 +701,8 @@ func (pm *ProcessManager) synchronizeMappings(pr process.Process, log.Debugf("Added %v mappings, removed %v mappings for PID %v with %d interpreters", len(mpAdd), len(mpRemove), pid, len(interpreters)) } - return newProcess -} - -// processPIDExit informs the ProcessManager that a process exited and no longer will be scheduled. -// exitKTime is stored for later processing in ProcessedUntil, when traces up to this time have been -// processed. There can be a race condition if we can not clean up the references for this process -// fast enough and this particular pid is reused again by the system. -func (pm *ProcessManager) processPIDExit(pid libpf.PID) { - exitKTime := times.GetKTime() - log.Debugf("- PID: %v", pid) - var err error - defer func() { - if err != nil { - log.Error(err) - } - }() - defer pm.ebpf.RemoveReportedPID(pid) - pm.mu.Lock() - defer pm.mu.Unlock() - - info, pidExists := pm.pidToProcessInfo[pid] - if !pidExists { - log.Debugf("Skip process exit handling for unknown PID %d", pid) - return - } - - // processPIDExit may be called multiple times in short succession - // for the same PID, don't update exitKTime if we've previously recorded it. - if _, pidExitProcessed := pm.exitEvents[pid]; !pidExitProcessed { - pm.exitEvents[pid] = exitKTime - } else { - log.Debugf("Skip duplicate process exit handling for PID %d", pid) - return - } - - // Delete all entries we have for this particular PID from pid_page_to_mapping_info. - deleted, err2 := pm.ebpf.DeletePidPageMappingInfo(pid, []lpm.Prefix{dummyPrefix}) - if err2 != nil { - err = errors.Join(err, fmt.Errorf("failed to delete dummy prefix for PID %d: %v", - pid, err2)) - } - pm.pidPageToMappingInfoSize -= uint64(deleted) - - for idx := range info.mappings { - pm.processRemovedMapping(pid, &info.mappings[idx]) - } - pm.processRemovedInterpreters(pid, libpf.Set[util.OnDiskFileIdentifier]{}) -} - -// SynchronizeProcess triggers ProcessManager to update its internal information -// about a process. This includes process exit information as well as changed memory mappings. -func (pm *ProcessManager) SynchronizeProcess(pr process.Process) { - pid := pr.PID() - log.Debugf("= PID: %v", pid) - - // Abort early if process is waiting for cleanup in ProcessedUntil - pm.mu.Lock() - _, ok := pm.exitEvents[pid] - pm.mu.Unlock() - - if ok { - log.Debugf("PID %v waiting for cleanup, aborting SynchronizeProcess", pid) - pm.ebpf.RemoveReportedPID(pid) - return - } - - pm.mappingStats.numProcAttempts.Add(1) - start := time.Now() - mappings, numParseErrors, err := pr.GetMappings() - elapsed := time.Since(start) - pm.mappingStats.numProcParseErrors.Add(numParseErrors) - - if err != nil { - if os.IsPermission(err) { - // Ignore the synchronization completely in case of permission - // error. This implies the process is still alive, but we cannot - // inspect it. Exiting here keeps the PID in the eBPF maps so - // we avoid a notification flood to resynchronize. - pm.mappingStats.errProcPerm.Add(1) - return - } - - if errors.Is(err, process.ErrNoMappings) { - // When no mappings can be extracted but the process is still alive, - // do not trigger a process exit to avoid unloading process metadata. - // As it's likely that a future iteration can extract mappings from a - // different thread in the process, notify eBPF to enable further notifications. - pm.ebpf.RemoveReportedPID(pid) - return - } - - // All other errors imply that the process has exited. - if os.IsNotExist(err) { - // Since listing /proc and opening files in there later is inherently racy, - // we expect to lose the race sometimes and thus expect to hit os.IsNotExist. - pm.mappingStats.errProcNotExist.Add(1) - } else if e, ok := err.(*os.PathError); ok && e.Err == syscall.ESRCH { - // If the process exits while reading its /proc/$PID/maps, the kernel will - // return ESRCH. Handle it as if the process did not exist. - pm.mappingStats.errProcESRCH.Add(1) - } - // Clean up, and notify eBPF. - log.Debugf("removing pid due to mappings read error: %v", err) - pm.processPIDExit(pid) - return - } - - util.AtomicUpdateMaxUint32(&pm.mappingStats.maxProcParseUsec, uint32(elapsed.Microseconds())) - pm.mappingStats.totalProcParseUsec.Add(uint32(elapsed.Microseconds())) - - if pm.synchronizeMappings(pr, mappings) { + if newProcess { log.Debugf("+ PID: %v", pid) // TODO: Fine-grained reported_pids handling (evaluate per-PID mapping // synchronization based on per-PID state such as time since last @@ -775,3 +827,24 @@ func (pm *ProcessManager) ProcessedUntil(traceCaptureKTime times.KTime) { log.Debugf("PID %v exit latency %v ms", pid, (nowKTime-pidExitKTime)/1e6) } } + +func readProcessContext(mappingAddr uint64, pr process.Process, oldProcessContextInfo processcontext.Info) processcontext.Info { + // Workaround to fix a CodeQL warning about potential for integer overflow when converting from uint64 to uintptr (libpf.Address) + addr := libpf.Address(mappingAddr & uint64(^libpf.Address(0))) + ctxInfo, err := processcontext.Read(addr, pr.GetRemoteMemory(), oldProcessContextInfo.PublishedAtNs, 0) + if err == nil { + return ctxInfo + } + if errors.Is(err, processcontext.ErrNoUpdate) { + return oldProcessContextInfo + } + if errors.Is(err, processcontext.ErrConcurrentUpdate) { + // If the context cannot be read because of a concurrent update, keep the resource and thread context since they are immutable, + // but discard the extra attributes as they may be stale. + oldProcessContextInfo.ClearExtraAttributes() + return oldProcessContextInfo + } + + log.Debugf("Failed to read ProcessContext for PID %d: %v", pr.PID(), err) + return processcontext.Info{} +} diff --git a/processmanager/processinfo_test.go b/processmanager/processinfo_test.go index 8d6647508..053200d59 100644 --- a/processmanager/processinfo_test.go +++ b/processmanager/processinfo_test.go @@ -63,7 +63,6 @@ func TestAssignLibcInfoMergesLibcInfo(t *testing.T) { libc.DTVInfo{ Offset: -8, Multiplier: 16, - Indirect: 1, }, } diff --git a/processmanager/types.go b/processmanager/types.go index ffe8e4ec5..c06dd0e5d 100644 --- a/processmanager/types.go +++ b/processmanager/types.go @@ -113,6 +113,16 @@ type ProcessManager struct { // includeEnvVars holds a list of env vars that should be captured from processes includeEnvVars libpf.Set[string] + + // selfCgroupIno is the inode of the profiler's cgroup directory + // (stat("/sys/fs/cgroup")). Used to identify processes whose cgroup root + // matches the profiler's, which need the selfContainerID fallback. + selfCgroupIno uint64 + + // selfContainerID is the profiler's own container ID, detected once at startup. + // Used as a fallback when /proc//cgroup yields no container ID for processes + // that share the profiler's cgroup directory (e.g., private cgroup namespace). + selfContainerID libpf.String } // Mapping represents an executable memory mapping of a process. diff --git a/reporter/base_reporter.go b/reporter/base_reporter.go index 84266918c..2ba4d4576 100644 --- a/reporter/base_reporter.go +++ b/reporter/base_reporter.go @@ -88,18 +88,20 @@ func (b *baseReporter) ReportTraceEvent(trace *libpf.Trace, meta *samples.TraceE Comm: meta.Comm, TID: int64(meta.TID), CPU: int64(meta.CPU), + SpanID: meta.SpanID, + TraceID: meta.TraceID, ExtraMeta: extraMeta, } if events, exists := rtp.Events[meta.Origin][sampleKey]; exists { events.Timestamps = append(events.Timestamps, uint64(meta.Timestamp)) - events.OffTimes = append(events.OffTimes, meta.OffTime) + events.Values = append(events.Values, meta.Value) return nil } rtp.Events[meta.Origin][sampleKey] = &samples.TraceEvents{ Frames: trace.Frames, Timestamps: []uint64{uint64(meta.Timestamp)}, - OffTimes: []int64{meta.OffTime}, + Values: []int64{meta.Value}, Labels: trace.CustomLabels, } return nil diff --git a/reporter/base_reporter_test.go b/reporter/base_reporter_test.go index 8b750a6f7..6120eb92f 100644 --- a/reporter/base_reporter_test.go +++ b/reporter/base_reporter_test.go @@ -112,7 +112,7 @@ func TestBaseReporterGenerate(t *testing.T) { TID: 2001, CPU: 1, Origin: support.TraceOriginOffCPU, - OffTime: 5000000, // 5ms + Value: 5000000, // 5ms } err := reporter.ReportTraceEvent(trace1, meta1) diff --git a/reporter/iface.go b/reporter/iface.go index d26b3f11e..f5291ef19 100644 --- a/reporter/iface.go +++ b/reporter/iface.go @@ -40,9 +40,9 @@ type ExecutableMetadata struct { // Process is the interface to the process holding the file. Process process.Process - // Mapping is the process.Mapping file. Process.OpenMappingFile can be used + // Mapping is the process.RawMapping file. Process.OpenMappingFile can be used // to open the file if needed. - Mapping *process.Mapping + Mapping *process.RawMapping // DebuglinkFileName is the path to the matching debug file // from the .gnu.debuglink, if any. The caller should diff --git a/reporter/internal/pdata/generate.go b/reporter/internal/pdata/generate.go index 830c77ca8..bc71212e8 100644 --- a/reporter/internal/pdata/generate.go +++ b/reporter/internal/pdata/generate.go @@ -61,6 +61,7 @@ func (p *Pdata) Generate(tree samples.TraceEventsTree, mappingSet := make(orderedset.OrderedSet[libpf.FrameMapping], 64) stackSet := make(orderedset.OrderedSet[stackInfo], 64) locationSet := make(orderedset.OrderedSet[locationInfo], 64) + linkSet := make(orderedset.OrderedSet[linkInfo], 64) // By specification, the first element should be empty. stringSet.Add("") @@ -68,6 +69,7 @@ func (p *Pdata) Generate(tree samples.TraceEventsTree, mappingSet.Add(libpf.FrameMapping{}) stackSet.Add(stackInfo{}) locationSet.Add(locationInfo{}) + linkSet.Add(linkInfo{}) dic.LinkTable().AppendEmpty() dic.MappingTable().AppendEmpty() @@ -103,7 +105,7 @@ func (p *Pdata) Generate(tree samples.TraceEventsTree, prof := sp.Profiles().AppendEmpty() if err := p.setProfile(dic, attrMgr, - stringSet, funcSet, mappingSet, stackSet, locationSet, + stringSet, funcSet, mappingSet, stackSet, locationSet, linkSet, origin, toEvents.Events[origin], prof, collectionStartTime, collectionEndTime); err != nil { return profiles, err @@ -143,6 +145,7 @@ func (p *Pdata) setProfile( mappingSet orderedset.OrderedSet[libpf.FrameMapping], stackSet orderedset.OrderedSet[stackInfo], locationSet orderedset.OrderedSet[locationInfo], + linkSet orderedset.OrderedSet[linkInfo], origin libpf.Origin, events samples.SampleToEvents, profile pprofile.Profile, @@ -174,7 +177,22 @@ func (p *Pdata) setProfile( sample.TimestampsUnixNano().FromRaw(traceInfo.Timestamps) if origin == support.TraceOriginOffCPU { - sample.Values().Append(traceInfo.OffTimes...) + sample.Values().Append(traceInfo.Values...) + } + + if sampleKey.SpanID != libpf.InvalidAPMSpanID && + sampleKey.TraceID != libpf.InvalidAPMTraceID { + link, ok := linkSet.AddWithCheck(linkInfo{ + traceID: sampleKey.TraceID, + spanID: sampleKey.SpanID, + }) + if !ok { + l := dic.LinkTable().AppendEmpty() + l.SetSpanID(pcommon.SpanID(sampleKey.SpanID)) + l.SetTraceID(pcommon.TraceID(sampleKey.TraceID)) + + } + sample.SetLinkIndex(link) } locationIndices := make([]int32, 0, len(traceInfo.Frames)) diff --git a/reporter/internal/pdata/generate_test.go b/reporter/internal/pdata/generate_test.go index f9fc35293..802fdf9dd 100644 --- a/reporter/internal/pdata/generate_test.go +++ b/reporter/internal/pdata/generate_test.go @@ -12,7 +12,7 @@ import ( v1profiles "go.opentelemetry.io/proto/otlp/profiles/v1development" "google.golang.org/protobuf/proto" - "github.com/open-telemetry/sig-profiling/tools/profcheck" + "github.com/open-telemetry/sig-profiling/profcheck" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" @@ -457,7 +457,7 @@ func TestGenerate_MultipleOriginsAndContainers(t *testing.T) { uint64(time.Unix(1030, 0).UnixNano()), uint64(time.Unix(1040, 0).UnixNano()), }, - OffTimes: []int64{10, 20}, + Values: []int64{10, 20}, }, }, } @@ -593,7 +593,15 @@ func TestGenerate_NativeFrame(t *testing.T) { } events := map[libpf.Origin]samples.SampleToEvents{ support.TraceOriginSampling: { - {}: &samples.TraceEvents{ + { + Hash: libpf.NewTraceHash(0, 1), + Comm: libpf.Intern("abc"), + TID: 42, + CPU: 73, + SpanID: libpf.APMSpanID{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, + TraceID: libpf.APMTraceID{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}, + }: &samples.TraceEvents{ Frames: singleFrameNative(mappingFile, 0x1000, 0x1000, 0x2000, 0x100), Timestamps: []uint64{ uint64(time.Unix(1010, 0).UnixNano()), @@ -668,6 +676,50 @@ func TestGenerate_NativeFrame(t *testing.T) { // since it's resolved by the backend. The function table should be empty. assert.Equal(t, 1, dic.FunctionTable().Len(), "Function table should be empty for native frames") + + // Verify SpanID and TraceID are set via Link + linkIndex := sample.LinkIndex() + assert.Greater(t, linkIndex, int32(0), "Sample should have a link set (index > 0, since 0 is dummy)") + link := dic.LinkTable().At(int(linkIndex)) + expectedSpanID := pcommon.SpanID{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7} + expectedTraceID := pcommon.TraceID{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27} + assert.Equal(t, expectedSpanID, link.SpanID()) + assert.Equal(t, expectedTraceID, link.TraceID()) + + // Verify Comm, TID, and CPU are set in sample attributes + attributeIndices := sample.AttributeIndices().AsRaw() + assert.NotEmpty(t, attributeIndices, "Sample should have attributes") + + attributeTable := dic.AttributeTable() + stringTable := dic.StringTable() + + foundComm := false + foundTID := false + foundCPU := false + + for _, attrIdx := range attributeIndices { + attr := attributeTable.At(int(attrIdx)) + keyStrIdx := attr.KeyStrindex() + key := stringTable.At(int(keyStrIdx)) + + switch key { + case string(semconv.ThreadNameKey): + assert.Equal(t, "abc", attr.Value().Str()) + foundComm = true + case string(semconv.ThreadIDKey): + assert.Equal(t, int64(42), attr.Value().Int()) + foundTID = true + case string(semconv.CPULogicalNumberKey): + assert.Equal(t, int64(73), attr.Value().Int()) + foundCPU = true + } + } + + assert.True(t, foundComm, "Sample should have Comm attribute set") + assert.True(t, foundTID, "Sample should have TID attribute set") + assert.True(t, foundCPU, "Sample should have CPU attribute set") + } func TestStackTableOrder(t *testing.T) { @@ -761,7 +813,15 @@ func TestGenerate_Validate(t *testing.T) { } events := map[libpf.Origin]samples.SampleToEvents{ support.TraceOriginSampling: { - {}: &samples.TraceEvents{ + { + Hash: libpf.NewTraceHash(0, 1), + Comm: libpf.Intern("abc"), + TID: 42, + CPU: 73, + SpanID: libpf.APMSpanID{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, + TraceID: libpf.APMTraceID{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}, + }: &samples.TraceEvents{ Frames: singleFrameTrace(libpf.PythonFrame, mapping, 0x30, funcName, filePath, 123), Timestamps: []uint64{42}, @@ -787,16 +847,6 @@ func TestGenerate_Validate(t *testing.T) { err = proto.Unmarshal(contents, &data) require.NoError(t, err) - // Fix for protobuf unmarshaling for ConformanceChecker: The first attribute - // table entry must have a nil Value,but protobuf unmarshaling creates a - // non-nil but empty AnyValue. Explicitly set it to nil. - if data.Dictionary != nil && len(data.Dictionary.AttributeTable) > 0 { - firstAttr := data.Dictionary.AttributeTable[0] - if firstAttr.KeyStrindex == 0 && firstAttr.UnitStrindex == 0 { - firstAttr.Value = nil - } - } - err = (profcheck.ConformanceChecker{ CheckDictionaryDuplicates: true, CheckSampleTimestampShape: true}).Check(&data) diff --git a/reporter/internal/pdata/helper.go b/reporter/internal/pdata/helper.go index 0a86418aa..7927f0f95 100644 --- a/reporter/internal/pdata/helper.go +++ b/reporter/internal/pdata/helper.go @@ -35,3 +35,9 @@ func hashLocationIndices(locationIndices []int32) uint64 { h.Write(pfunsafe.FromSlice(locationIndices)) return h.Sum64() } + +// linkInfo is a helper used to deduplicate Links. +type linkInfo struct { + spanID libpf.APMSpanID + traceID libpf.APMTraceID +} diff --git a/reporter/samples/samples.go b/reporter/samples/samples.go index a0f08b2c8..0287e1f18 100644 --- a/reporter/samples/samples.go +++ b/reporter/samples/samples.go @@ -17,8 +17,10 @@ type TraceEventMeta struct { Timestamp libpf.UnixTime64 CPU int Origin libpf.Origin - OffTime int64 + Value int64 PID, TID libpf.PID + SpanID libpf.APMSpanID + TraceID libpf.APMTraceID } // TraceEvents holds known information about a trace. @@ -26,7 +28,7 @@ type TraceEvents struct { Labels map[libpf.String]libpf.String Frames libpf.Frames Timestamps []uint64 // in nanoseconds - OffTimes []int64 // in nanoseconds + Values []int64 } // TraceEventsTree stores samples and their related metadata in a tree-like @@ -78,4 +80,7 @@ type SampleKey struct { TID int64 CPU int64 + + SpanID libpf.APMSpanID + TraceID libpf.APMTraceID } diff --git a/rust-crates/symblib/src/fileid.rs b/rust-crates/symblib/src/fileid.rs index cb1473cc9..a0a5770a6 100644 --- a/rust-crates/symblib/src/fileid.rs +++ b/rust-crates/symblib/src/fileid.rs @@ -6,7 +6,6 @@ use base64::Engine; use sha2::digest::FixedOutput; use sha2::Digest as _; -use std::io::Read as _; use std::{fmt, fs, io, path}; /// Size of the head and tail blocks used for partially hashing ELF files. @@ -44,12 +43,17 @@ impl FileId { // Hash first 4096 bytes. stream.seek(io::SeekFrom::Start(0))?; - io::copy(&mut stream.by_ref().take(PARTIAL_HASH_SIZE), &mut hasher)?; + let mut buf = [0u8; 4096]; + let to_read = (PARTIAL_HASH_SIZE as usize).min(buf.len()); + let n = stream.read(&mut buf[..to_read])?; + hasher.update(&buf[..n]); // Hash last 4096 bytes. let tail_start = stream_len.saturating_sub(PARTIAL_HASH_SIZE); stream.seek(io::SeekFrom::Start(tail_start))?; - io::copy(&mut stream.by_ref().take(PARTIAL_HASH_SIZE), &mut hasher)?; + let to_read = (PARTIAL_HASH_SIZE as usize).min(buf.len()); + let n = stream.read(&mut buf[..to_read])?; + hasher.update(&buf[..n]); // Hash length. hasher.update(u64::to_be_bytes(stream_len)); diff --git a/support/ebpf/beam_tracer.ebpf.c b/support/ebpf/beam_tracer.ebpf.c index 7ca6088b0..9cac2cae6 100644 --- a/support/ebpf/beam_tracer.ebpf.c +++ b/support/ebpf/beam_tracer.ebpf.c @@ -3,7 +3,7 @@ #include "types.h" // The number of frames to unwind per frame-unwinding eBPF program. -#define BEAM_FRAMES_PER_PROGRAM 8 +#define BEAM_FRAMES_PER_PROGRAM 60 // The max number of loops to unroll when searching for the correct CodeHeader. // Should be log base 2 of a reasonable number of modules to binary-search through. diff --git a/support/ebpf/bpfdefs.h b/support/ebpf/bpfdefs.h index 0a786df80..96d560082 100644 --- a/support/ebpf/bpfdefs.h +++ b/support/ebpf/bpfdefs.h @@ -46,6 +46,12 @@ static inline long bpf_probe_read_user(void *buf, u32 sz, const void *ptr) return __bpf_probe_read_user(__cgo_ctx->id, buf, sz, ptr); } +static inline long bpf_probe_read_user_with_test_fault(void *buf, u32 sz, const void *ptr) +{ + long __bpf_probe_read_user_with_test_fault(u64, void *, u32, const void *); + return __bpf_probe_read_user_with_test_fault(__cgo_ctx->id, buf, sz, ptr); +} + static inline long bpf_probe_read_kernel(UNUSED void *buf, UNUSED u32 sz, UNUSED const void *ptr) { return -1; @@ -122,12 +128,11 @@ static long (*bpf_probe_read_user)(void *dst, int size, const void *unsafe_ptr) static long (*bpf_probe_read_kernel)(void *dst, int size, const void *unsafe_ptr) = (void *) BPF_FUNC_probe_read_kernel; - // The sizeof in bpf_trace_printk() must include \0, else no output - // is generated. The \n is not needed on 5.8+ kernels, but definitely on - // 5.4 kernels. + #define bpf_probe_read_user_with_test_fault bpf_probe_read_user + #define printt(fmt, ...) \ ({ \ - const char ____fmt[] = fmt "\n"; \ + static const char ____fmt[] = fmt; \ bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \ }) diff --git a/support/ebpf/dotnet_tracer.ebpf.c b/support/ebpf/dotnet_tracer.ebpf.c index d88f06e66..ed3fc3a6f 100644 --- a/support/ebpf/dotnet_tracer.ebpf.c +++ b/support/ebpf/dotnet_tracer.ebpf.c @@ -10,10 +10,10 @@ #include "types.h" // The number of dotnet frames to unwind per frame-unwinding eBPF program. -#define DOTNET_FRAMES_PER_PROGRAM 6 +#define DOTNET_FRAMES_PER_PROGRAM 45 // The number of dotnet10+ frames to unwind per frame-unwinding eBPF program. -#define DOTNET10_FRAMES_PER_PROGRAM 9 +#define DOTNET10_FRAMES_PER_PROGRAM 68 // The maximum dotnet frame length used in heuristic to validate FP #define DOTNET_MAX_FRAME_LENGTH 8192 diff --git a/support/ebpf/frametypes.h b/support/ebpf/frametypes.h index aae841d98..3d7f90cfc 100644 --- a/support/ebpf/frametypes.h +++ b/support/ebpf/frametypes.h @@ -33,6 +33,8 @@ #define FRAME_MARKER_GO 0xB // Indicates a BEAM frame #define FRAME_MARKER_BEAM 0xC +// Indicates a LuaJIT frame +#define FRAME_MARKER_LUAJIT 0xD // Frame flags // Indicates that this frame is an error frame. diff --git a/support/ebpf/hotspot_tracer.ebpf.c b/support/ebpf/hotspot_tracer.ebpf.c index 6bdc2d2f7..afbb3f1cc 100644 --- a/support/ebpf/hotspot_tracer.ebpf.c +++ b/support/ebpf/hotspot_tracer.ebpf.c @@ -69,7 +69,7 @@ typedef enum HotspotUnwindAction { } HotspotUnwindAction; // The number of hotspot frames to unwind per frame-unwinding eBPF program. -#define HOTSPOT_FRAMES_PER_PROGRAM 4 +#define HOTSPOT_FRAMES_PER_PROGRAM 30 // The maximum number of HotSpot segmap lookup iterations. This is directly proportional // to the size of JIT method code size. The longest sequence seen so far is from JDK8, diff --git a/support/ebpf/interpreter_dispatcher.ebpf.c b/support/ebpf/interpreter_dispatcher.ebpf.c index aa2d54c24..8bcfb7b25 100644 --- a/support/ebpf/interpreter_dispatcher.ebpf.c +++ b/support/ebpf/interpreter_dispatcher.ebpf.c @@ -34,7 +34,7 @@ struct perf_progs_t { __uint(max_entries, NUM_TRACER_PROGS); } perf_progs SEC(".maps"); -// report_events notifies user space about events (GENERIC_PID and RELOAD_KALLSYMS). +// report_events notifies user space about events (GENERIC_PID). // // As a key the CPU number is used and the value represents a perf event file descriptor. // Information transmitted is the event type only. We use 0 as the number of max entries @@ -118,6 +118,15 @@ struct trace_events_t { // End shared maps +// Implements the specification to share span/trace IDs according to: +// https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/main/devdocs/trace-profile-correlation.md +struct traces_ctx_v1_t { + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __type(key, u64); + __type(value, SpanTraceInfo); + __uint(max_entries, 1 << 14); +} traces_ctx_v1 SEC(".maps"); + struct apm_int_procs_t { __uint(type, BPF_MAP_TYPE_HASH); __type(key, pid_t); @@ -188,6 +197,26 @@ static EBPF_INLINE void maybe_add_go_custom_labels(struct pt_regs *ctx, PerCPURe tail_call(ctx, PROG_GO_LABELS); } +// Implements the specification to share span/trace IDs according to: +// https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/main/devdocs/trace-profile-correlation.md +static EBPF_INLINE void maybe_add_otel_span_trace_id(Trace *trace) +{ + u64 id = bpf_get_current_pid_tgid(); + + SpanTraceInfo *info = bpf_map_lookup_elem(&traces_ctx_v1, &id); + if (!info) { + return; + } + + // The structure of apm_[transaction|trace]_id happens to be the same + // as proposed in + // https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/main/devdocs/trace-profile-correlation.md + + trace->apm_trace_id.as_int.hi = info->trace_id.as_int.hi; + trace->apm_trace_id.as_int.lo = info->trace_id.as_int.lo; + trace->apm_transaction_id.as_int = info->span_id.as_int; +} + static EBPF_INLINE void maybe_add_apm_info(Trace *trace) { u32 pid = trace->pid; // verifier needs this to be on stack on 4.15 kernel @@ -247,6 +276,12 @@ static EBPF_INLINE int unwind_stop(struct pt_regs *ctx) UnwindState *state = &record->state; maybe_add_apm_info(trace); + if ( + trace->apm_trace_id.as_int.hi == 0 && trace->apm_trace_id.as_int.lo == 0 && + trace->apm_transaction_id.as_int == 0) { + // Populate OTel span/trace ID only if span/trace ID is not yet set. + maybe_add_otel_span_trace_id(trace); + } // If the stack is otherwise empty, push an error for that: we should // never encounter empty stacks for successful unwinding. diff --git a/support/ebpf/kallsyms.ebpf.c b/support/ebpf/kallsyms.ebpf.c deleted file mode 100644 index 8e2eee667..000000000 --- a/support/ebpf/kallsyms.ebpf.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "bpfdefs.h" -#include "tracemgmt.h" -#include "types.h" - -// kprobe__kallsyms notifies user space about changes to kallsyms. -SEC("kprobe/kallsysms") -int kprobe__kallsyms(void *ctx) -{ - event_send_trigger(ctx, EVENT_TYPE_RELOAD_KALLSYMS); - return 0; -} diff --git a/support/ebpf/native_stack_trace.ebpf.c b/support/ebpf/native_stack_trace.ebpf.c index 5fd291634..1c015b77e 100644 --- a/support/ebpf/native_stack_trace.ebpf.c +++ b/support/ebpf/native_stack_trace.ebpf.c @@ -75,7 +75,7 @@ struct unwind_info_array_t { } unwind_info_array SEC(".maps"); // The number of native frames to unwind per frame-unwinding eBPF program. -#define NATIVE_FRAMES_PER_PROGRAM 5 +#define NATIVE_FRAMES_PER_PROGRAM 10 // The decision whether to unwind native stacks or interpreter stacks is made by checking if a given // PC address falls into the "interpreter loop" of an interpreter. This map helps identify such @@ -157,22 +157,17 @@ static EBPF_INLINE void *get_stack_delta_map(int mapID) // Get the stack offset of the given instruction. static EBPF_INLINE ErrorCode get_stack_delta(UnwindState *state, int *addrDiff, u32 *unwindInfo) { - u64 exe_id = state->text_section_id; + unsigned long exe_id = state->text_section_id; + unsigned long offset = state->text_section_offset - (int)state->return_address; // Look up the stack delta page information for this address. StackDeltaPageKey key = {}; - key.fileID = state->text_section_id; - key.page = state->text_section_offset & ~STACK_DELTA_PAGE_MASK; - DEBUG_PRINT( - "Look up stack delta for %lx:%lx", - (unsigned long)state->text_section_id, - (unsigned long)state->text_section_offset); + key.fileID = exe_id; + key.page = offset & ~STACK_DELTA_PAGE_MASK; + DEBUG_PRINT("Look up stack delta for %lx:%lx", exe_id, offset); StackDeltaPageInfo *info = bpf_map_lookup_elem(&stack_delta_page_to_info, &key); if (!info) { - DEBUG_PRINT( - "Failure to look up stack delta page fileID %lx, page %lx", - (unsigned long)key.fileID, - (unsigned long)key.page); + DEBUG_PRINT("Failure to look up stack delta page fileID %lx, page %lx", exe_id, offset); state->error_metric = metricID_UnwindNativeErrLookupTextSection; return ERR_NATIVE_LOOKUP_TEXT_SECTION; } @@ -180,23 +175,21 @@ static EBPF_INLINE ErrorCode get_stack_delta(UnwindState *state, int *addrDiff, void *outer_map = get_stack_delta_map(info->mapID); if (!outer_map) { DEBUG_PRINT( - "Failure to look up outer map for text section %lx in mapID %d", - (unsigned long)exe_id, - (int)info->mapID); + "Failure to look up outer map for text section %lx in mapID %d", exe_id, (int)info->mapID); state->error_metric = metricID_UnwindNativeErrLookupStackDeltaOuterMap; return ERR_NATIVE_LOOKUP_STACK_DELTA_OUTER_MAP; } void *inner_map = bpf_map_lookup_elem(outer_map, &exe_id); if (!inner_map) { - DEBUG_PRINT("Failure to look up inner map for text section %lx", (unsigned long)exe_id); + DEBUG_PRINT("Failure to look up inner map for text section %lx", exe_id); state->error_metric = metricID_UnwindNativeErrLookupStackDeltaInnerMap; return ERR_NATIVE_LOOKUP_STACK_DELTA_INNER_MAP; } // Preinitialize the idx for the index to use for page without any deltas. u32 idx = info->firstDelta; - u16 page_offset = state->text_section_offset & STACK_DELTA_PAGE_MASK; + u16 page_offset = offset & STACK_DELTA_PAGE_MASK; if (info->numDeltas) { // Page has deltas, so find the correct one to use using binary search. u32 lo = info->firstDelta; @@ -321,14 +314,14 @@ unwind_calc_register_with_deref(UnwindState *state, u8 baseReg, s32 param, bool // is marked with UNWIND_COMMAND_STOP which marks entry points (main function, // thread spawn function, signal handlers, ...). #if defined(__x86_64__) -static EBPF_INLINE ErrorCode unwind_one_frame(UnwindState *state, bool *stop) +static EBPF_INLINE ErrorCode unwind_one_frame(PerCPURecord *record, bool *stop) { *stop = false; - u32 unwindInfo = 0; - u64 rt_regs[18]; - int addrDiff = 0; - u64 cfa = 0; + UnwindState *state = &record->state; + u32 unwindInfo = 0; + int addrDiff = 0; + u64 cfa = 0; // The relevant executable is compiled with frame pointer omission, so // stack deltas need to be retrieved from the relevant map. @@ -347,14 +340,19 @@ static EBPF_INLINE ErrorCode unwind_one_frame(UnwindState *state, bool *stop) cfa = state->sp + 8 + ((((state->pc & 15) >= 11) ? 1 : 0) << 3); DEBUG_PRINT("PLT, cfa=0x%lx", (unsigned long)cfa); break; - case UNWIND_COMMAND_SIGNAL: + case UNWIND_COMMAND_SIGNAL: { + // Use the PerCPURecord scratch union instead of a stack-local buffer to avoid + // exceeding the 512-byte BPF stack limit when inlined into interpreters. + u64 *rt_regs = record->rt_regs; // The rt_sigframe is defined at: // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/sigframe.h?h=v6.4#n59 // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/uapi/asm/sigcontext.h?h=v6.4#n238 // offsetof(struct rt_sigframe, uc.uc_mcontext) = 40 - if (bpf_probe_read_user(&rt_regs, sizeof(rt_regs), (void *)(state->sp + 40))) { + if (bpf_probe_read_user(rt_regs, sizeof(record->rt_regs), (void *)(state->sp + 40))) { goto err_native_pc_read; } + state->rdi = rt_regs[8]; + state->r8 = rt_regs[0]; state->rax = rt_regs[13]; state->r9 = rt_regs[1]; state->r11 = rt_regs[3]; @@ -367,6 +365,7 @@ static EBPF_INLINE ErrorCode unwind_one_frame(UnwindState *state, bool *stop) state->return_address = false; DEBUG_PRINT("signal frame"); goto frame_ok; + } case UNWIND_COMMAND_STOP: *stop = true; return ERR_OK; case UNWIND_COMMAND_FRAME_POINTER: if (!unwinder_unwind_frame_pointer(state)) { @@ -395,10 +394,18 @@ static EBPF_INLINE ErrorCode unwind_one_frame(UnwindState *state, bool *stop) // the previous FP address if any. state->cfa = cfa = unwind_calc_register_with_deref( state, info->baseReg, param, (info->flags & UNWIND_FLAG_DEREF_CFA) != 0); - u64 fpa = unwind_calc_register(state, info->auxBaseReg, info->auxParam); + u64 aux = unwind_calc_register(state, info->auxBaseReg, info->auxParam); - if (fpa) { - bpf_probe_read_user(&state->fp, sizeof(state->fp), (void *)fpa); + if (info->flags & UNWIND_FLAG_REGISTER_RA) { + // RA was recovered from a register (e.g. __vfork stores RA in %rdi). + // FP is not preserved across such calls, clear it for the next frame. + state->pc = aux; + state->fp = 0; + goto nonleaf_frame_ok; + } + + if (aux) { + bpf_probe_read_user(&state->fp, sizeof(state->fp), (void *)aux); } else if (info->baseReg == UNWIND_REG_FP) { // FP used for recovery, but no new FP value received, clear FP state->fp = 0; @@ -410,6 +417,7 @@ static EBPF_INLINE ErrorCode unwind_one_frame(UnwindState *state, bool *stop) increment_metric(metricID_UnwindNativeErrPCRead); return ERR_NATIVE_PC_READ; } +nonleaf_frame_ok: state->sp = cfa; unwinder_mark_nonleaf_frame(state); frame_ok: @@ -417,13 +425,13 @@ static EBPF_INLINE ErrorCode unwind_one_frame(UnwindState *state, bool *stop) return ERR_OK; } #elif defined(__aarch64__) -static EBPF_INLINE ErrorCode unwind_one_frame(struct UnwindState *state, bool *stop) +static EBPF_INLINE ErrorCode unwind_one_frame(PerCPURecord *record, bool *stop) { *stop = false; - u32 unwindInfo = 0; - int addrDiff = 0; - u64 rt_regs[34]; + UnwindState *state = &record->state; + u32 unwindInfo = 0; + int addrDiff = 0; // The relevant executable is compiled with frame pointer omission, so // stack deltas need to be retrieved from the relevant map. @@ -434,7 +442,10 @@ static EBPF_INLINE ErrorCode unwind_one_frame(struct UnwindState *state, bool *s if (unwindInfo & STACK_DELTA_COMMAND_FLAG) { switch (unwindInfo & ~STACK_DELTA_COMMAND_FLAG) { - case UNWIND_COMMAND_SIGNAL: + case UNWIND_COMMAND_SIGNAL: { + // Use the PerCPURecord scratch union instead of a stack-local buffer to avoid + // exceeding the 512-byte BPF stack limit when inlined into interpreters. + u64 *rt_regs = record->rt_regs; // On aarch64 the struct rt_sigframe is at: // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm64/kernel/signal.c?h=v6.4#n39 // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm64/include/uapi/asm/sigcontext.h?h=v6.4#n28 @@ -442,7 +453,7 @@ static EBPF_INLINE ErrorCode unwind_one_frame(struct UnwindState *state, bool *s // offsetof(struct rt_sigframe, uc) 128 + // offsetof(struct ucontext, uc_mcontext) 176 + // offsetof(struct sigcontext, regs[0]) 8 - if (bpf_probe_read_user(&rt_regs, sizeof(rt_regs), (void *)(state->sp + 312))) { + if (bpf_probe_read_user(rt_regs, sizeof(record->rt_regs), (void *)(state->sp + 312))) { goto err_native_pc_read; } state->pc = normalize_pac_ptr(rt_regs[32]); @@ -457,6 +468,7 @@ static EBPF_INLINE ErrorCode unwind_one_frame(struct UnwindState *state, bool *s state->lr_invalid = false; DEBUG_PRINT("signal frame"); goto frame_ok; + } case UNWIND_COMMAND_STOP: *stop = true; return ERR_OK; case UNWIND_COMMAND_FRAME_POINTER: if (!unwinder_unwind_frame_pointer(state)) { @@ -573,7 +585,7 @@ static EBPF_INLINE int unwind_native(struct pt_regs *ctx) // Unwind the native frame using stack deltas. Stop if no next frame. bool stop; - error = unwind_one_frame(&record->state, &stop); + error = unwind_one_frame(record, &stop); if (error || stop) { break; } diff --git a/support/ebpf/off_cpu.ebpf.c b/support/ebpf/off_cpu.ebpf.c index 2690bf1c2..ab7cfd74a 100644 --- a/support/ebpf/off_cpu.ebpf.c +++ b/support/ebpf/off_cpu.ebpf.c @@ -82,6 +82,7 @@ int finish_task_switch(struct pt_regs *ctx) // reported accidentally without the start timestamp updated in tracepoint/sched/sched_switch. bpf_map_delete_elem(&sched_times, &pid_tgid); + // diff stores the nanoseconds that the trace was off-cpu for. u64 diff = ts - *start_ts; DEBUG_PRINT("==== finish_task_switch ===="); diff --git a/support/ebpf/perl_tracer.ebpf.c b/support/ebpf/perl_tracer.ebpf.c index 9c22f5f38..164cca4f5 100644 --- a/support/ebpf/perl_tracer.ebpf.c +++ b/support/ebpf/perl_tracer.ebpf.c @@ -34,7 +34,7 @@ #include "types.h" // The number of Perl frames to unwind per frame-unwinding eBPF program. -#define PERL_FRAMES_PER_PROGRAM 12 +#define PERL_FRAMES_PER_PROGRAM 90 // PERL SI types definitions // https://github.com/Perl/perl5/blob/v5.32.0/cop.h#L1017-L1035 diff --git a/support/ebpf/php_tracer.ebpf.c b/support/ebpf/php_tracer.ebpf.c index ce9687f7c..8c3a898b6 100644 --- a/support/ebpf/php_tracer.ebpf.c +++ b/support/ebpf/php_tracer.ebpf.c @@ -7,7 +7,7 @@ // The number of PHP frames to unwind per frame-unwinding eBPF program. If // we start running out of instructions in the walk_php_stack program, one // option is to adjust this number downwards. -#define FRAMES_PER_WALK_PHP_STACK 19 +#define FRAMES_PER_WALK_PHP_STACK 143 // The type_info flag for executor data to indicate top-of-stack frames // as defined in php/Zend/zend_compile.h. diff --git a/support/ebpf/python_tracer.ebpf.c b/support/ebpf/python_tracer.ebpf.c index cab564897..2f261a450 100644 --- a/support/ebpf/python_tracer.ebpf.c +++ b/support/ebpf/python_tracer.ebpf.c @@ -9,7 +9,7 @@ // The number of Python frames to unwind per frame-unwinding eBPF program. If // we start running out of instructions in the walk_python_stack program, one // option is to adjust this number downwards. -#define FRAMES_PER_WALK_PYTHON_STACK 12 +#define FRAMES_PER_WALK_PYTHON_STACK 90 // Forward declaration to avoid warnings like // "declaration of 'struct pt_regs' will not be visible outside of this function [-Wvisibility]". @@ -141,10 +141,15 @@ static EBPF_INLINE ErrorCode process_python_frame( } // Read PyCodeObject - if (bpf_probe_read_user(pss->code, sizeof(pss->code), py_codeobject)) { + if (bpf_probe_read_user_with_test_fault(pss->code, sizeof(pss->code), py_codeobject)) { DEBUG_PRINT("Failed to read PyCodeObject at 0x%lx", (unsigned long)(py_codeobject)); increment_metric(metricID_UnwindPythonErrBadCodeObjectArgCountAddr); - return ERR_PYTHON_BAD_CODE_OBJECT_ADDR; + // Push the frame with the code object address so the agent can try to + // read it in userspace (which can take page faults unlike BPF). + // codeobject_id=0 distinguishes this from a successful read. + file_id = (u64)py_codeobject; + lineno = py_encode_lineno(0, (u32)py_f_lasti); + goto push_frame; } int py_argcount = *(int *)(&pss->code[pyinfo->PyCodeObject_co_argcount]); diff --git a/support/ebpf/ruby_tracer.ebpf.c b/support/ebpf/ruby_tracer.ebpf.c index 0d3a7b8c3..f19e2fb12 100644 --- a/support/ebpf/ruby_tracer.ebpf.c +++ b/support/ebpf/ruby_tracer.ebpf.c @@ -18,7 +18,7 @@ struct ruby_procs_t { // we start running out of instructions in the walk_ruby_stack program, one // option is to adjust this number downwards. // NOTE: the maximum size stack is FRAMES_PER_WALK_RUBY_STACK * calls to tail_call(). -#define FRAMES_PER_WALK_RUBY_STACK 32 +#define FRAMES_PER_WALK_RUBY_STACK 240 // When resolving a CME, we need to traverse environment pointers until we // find IMEMO_MENT. Since we can't do a while loop, we have to bound this // the max encountered in experimentation on a production rails app is 6. @@ -515,6 +515,30 @@ static EBPF_INLINE int unwind_ruby(struct pt_regs *ctx) ¤t_ctx_addr, sizeof(current_ctx_addr), (void *)(tls_current_ec_addr))) { goto exit; } + DEBUG_PRINT("ruby: EC from TLS: 0x%llx", (u64)current_ctx_addr); + } else if (rubyinfo->tls_module_id != 0 && rubyinfo->dtv_info.multiplier != 0) { + // DTV-based TLS access: traverse the Dynamic Thread Vector to find ruby_current_ec. + // Used when TLSDESC relocations are unavailable (e.g. some musl setups, + // or when TLS is allocated dynamically). + u64 tsd_base; + if (tsd_get_base((void **)&tsd_base) != 0) { + DEBUG_PRINT("ruby: failed to get TSD base for DTV lookup"); + error = ERR_RUBY_READ_TSD_BASE; + goto exit; + } + + void *ec_ptr; + if (dtv_read( + &rubyinfo->dtv_info, + (void *)tsd_base, + rubyinfo->tls_module_id, + rubyinfo->current_ec_tls_offset, + &ec_ptr)) { + DEBUG_PRINT("ruby: failed to read EC from DTV"); + goto exit; + } + current_ctx_addr = ec_ptr; + DEBUG_PRINT("ruby: EC from DTV: 0x%llx", (u64)current_ctx_addr); } else if (rubyinfo->version >= 0x30000) { void *single_main_ractor = NULL; if (bpf_probe_read_user( diff --git a/support/ebpf/system_config.ebpf.c b/support/ebpf/system_config.ebpf.c index d29d8c662..7d81e8169 100644 --- a/support/ebpf/system_config.ebpf.c +++ b/support/ebpf/system_config.ebpf.c @@ -33,15 +33,15 @@ int read_kernel_memory(UNUSED void *ctx) return 0; } - // Mark request handled - sys->pid = 0; - // Handle the read request - if (bpf_probe_read_kernel(sys->code, sizeof(sys->code), (void *)sys->address)) { - DEBUG_PRINT("Failed to read code from 0x%lx", (unsigned long)sys->address); - return -1; + sys->err = bpf_probe_read_kernel(sys->code, sizeof(sys->code), (void *)sys->address); + if (sys->err) { + DEBUG_PRINT("Failed to read code from 0x%lx: %ld", (unsigned long)sys->address, (long)sys->err); } + // Mark request handled once the helper has finished populating the result. + sys->pid = 0; + return 0; } @@ -65,9 +65,6 @@ int read_task_struct(struct bpf_raw_tracepoint_args *ctx) return 0; } - // Mark request handled - sys->pid = 0; - // Request to read current task. Adjust read address, and return // also the address of struct pt_regs in the entry stack. u64 addr = bpf_get_current_task() + sys->address; @@ -78,11 +75,14 @@ int read_task_struct(struct bpf_raw_tracepoint_args *ctx) sys->address = (u64)regs; // Execute the read request. - if (bpf_probe_read_kernel(sys->code, sizeof(sys->code), (void *)addr)) { - DEBUG_PRINT("Failed to read task_struct from 0x%lx", (unsigned long)addr); - return -1; + sys->err = bpf_probe_read_kernel(sys->code, sizeof(sys->code), (void *)addr); + if (sys->err) { + DEBUG_PRINT("Failed to read task_struct from 0x%lx: %ld", (unsigned long)addr, (long)sys->err); } + // Mark request handled once the helper has finished populating the result. + sys->pid = 0; + return 0; } diff --git a/support/ebpf/tracemgmt.h b/support/ebpf/tracemgmt.h index 24ba91d40..40e1b9c69 100644 --- a/support/ebpf/tracemgmt.h +++ b/support/ebpf/tracemgmt.h @@ -53,8 +53,7 @@ static inline EBPF_INLINE void increment_metric(u32 metricID) } // Send immediate notifications for event triggers to Go. -// Notifications for GENERIC_PID and RELOAD_KALLSYMS will be -// automatically inhibited until HA resets the type. +// Notifications for GENERIC_PID will be automatically inhibited until HA resets the type. static inline EBPF_INLINE void event_send_trigger(struct pt_regs *ctx, u32 event_type) { int inhibit_key = event_type; @@ -65,10 +64,18 @@ static inline EBPF_INLINE void event_send_trigger(struct pt_regs *ctx, u32 event // To avoid redundant notifications while userspace processing for them is already taking // place, we allow latch-like inhibition, where eBPF sets it and Go has to manually reset // it, before new notifications are triggered. - if (bpf_map_update_elem(&inhibit_events, &inhibit_key, &inhibit_value, BPF_NOEXIST) < 0) { + // + // Check the latch with a lock-free lookup first to avoid taking the hash bucket lock + // on every call. The lock is only taken on the first insert after Go resets the latch. + // There is a TOCTOU race window here which shouldn't affect correctness for PID events: + // Userspace will periodically drain the PID events map regardless of notification. + if (bpf_map_lookup_elem(&inhibit_events, &inhibit_key)) { DEBUG_PRINT("Event type %d inhibited", event_type); return; } + if (bpf_map_update_elem(&inhibit_events, &inhibit_key, &inhibit_value, BPF_NOEXIST) < 0) { + return; + } switch (event_type) { case EVENT_TYPE_GENERIC_PID: increment_metric(metricID_NumGenericPID); break; @@ -109,6 +116,10 @@ static inline EBPF_INLINE bool pid_information_exists(int pid) // based on rate limiting rules. static inline EBPF_INLINE bool pid_event_ratelimit(u32 pid, int ratelimit_action) { + if (ratelimit_action == RATELIMIT_ACTION_RESET) { + return false; + } + const u8 default_max_attempts = 8; // 25 seconds const u8 fast_max_attempts = 4; // 1.6 seconds const u8 fast_timer_flag = 0x10; @@ -117,10 +128,6 @@ static inline EBPF_INLINE bool pid_event_ratelimit(u32 pid, int ratelimit_action u8 attempt = 0; u8 fast_timer = (ratelimit_action == RATELIMIT_ACTION_FAST) ? fast_timer_flag : 0; - if (ratelimit_action == RATELIMIT_ACTION_RESET) { - return false; - } - if (token_ptr) { u64 token = *token_ptr; u64 diff_ts = ts - (token & ~0x1fULL); @@ -168,8 +175,7 @@ static inline EBPF_INLINE bool pid_event_ratelimit(u32 pid, int ratelimit_action } // report_pid informs userspace about a PID that needs to be processed. -// If inhibit is true, PID will first be checked against maps/reported_pids -// and reporting aborted if PID has been recently reported. +// See pid_event_ratelimit for ratelimit_action functional specifics. // Returns true if the PID was successfully reported to user space. static inline EBPF_INLINE bool report_pid(void *ctx, u64 pid_tgid, int ratelimit_action) { @@ -603,6 +609,8 @@ copy_state_regs(UnwindState *state, struct pt_regs *regs, bool interrupted_kerne state->sp = regs->sp; state->fp = regs->bp; state->rax = regs->ax; + state->rdi = regs->di; + state->r8 = regs->r8; state->r9 = regs->r9; state->r11 = regs->r11; state->r13 = regs->r13; @@ -733,7 +741,7 @@ get_usermode_regs(struct pt_regs *ctx, UnwindState *state, bool *has_usermode_re #endif // TESTING_COREDUMP static inline EBPF_INLINE int collect_trace( - struct pt_regs *ctx, TraceOrigin origin, u32 pid, u32 tid, u64 trace_timestamp, u64 off_cpu_time) + struct pt_regs *ctx, TraceOrigin origin, u32 pid, u32 tid, u64 trace_timestamp, u64 value) { // The trace is reused on each call to this function so we have to reset the // variables used to maintain state. @@ -743,12 +751,12 @@ static inline EBPF_INLINE int collect_trace( return -1; } - Trace *trace = &record->trace; - trace->origin = origin; - trace->pid = pid; - trace->tid = tid; - trace->ktime = trace_timestamp; - trace->offtime = off_cpu_time; + Trace *trace = &record->trace; + trace->origin = origin; + trace->pid = pid; + trace->tid = tid; + trace->ktime = trace_timestamp; + trace->value = value; if (bpf_get_current_comm(&(trace->comm), sizeof(trace->comm)) < 0) { increment_metric(metricID_ErrBPFCurrentComm); } diff --git a/support/ebpf/tracer.ebpf.amd64 b/support/ebpf/tracer.ebpf.amd64 index f24cb4dd0..631f4ef7c 100644 Binary files a/support/ebpf/tracer.ebpf.amd64 and b/support/ebpf/tracer.ebpf.amd64 differ diff --git a/support/ebpf/tracer.ebpf.arm64 b/support/ebpf/tracer.ebpf.arm64 index 42aa86950..89e4c382c 100644 Binary files a/support/ebpf/tracer.ebpf.arm64 and b/support/ebpf/tracer.ebpf.arm64 differ diff --git a/support/ebpf/tsd.h b/support/ebpf/tsd.h index 33dd1b9fd..28accaf76 100644 --- a/support/ebpf/tsd.h +++ b/support/ebpf/tsd.h @@ -32,6 +32,57 @@ tsd_read(const TSDInfo *tsi, const void *tsd_base, int key, void **out) return -1; } +// dtv_read reads a TLS variable by traversing the Dynamic Thread Vector (DTV). +// The DTV is an array of pointers to per-module TLS blocks, indexed by TLS module ID. +// On x86_64 the default TLS dialect uses General Dynamic (GD) relocations +// (R_X86_64_DTPMOD64) rather than TLSDESC, so the DTV path is the primary +// mechanism for resolving thread-local variables in shared libraries. +// This path is also needed on other platforms when TLSDESC is unavailable. +// +// Parameters: +// dtvi: DTVInfo extracted from __tls_get_addr disassembly (offset, multiplier) +// tsd_base: thread pointer base (from tsd_get_base) +// module_id: TLS module ID for the target DSO (from DTPMOD64 relocation) +// tls_offset: offset of the variable within its module's TLS block +// out: pointer to store the result +static inline EBPF_INLINE int +dtv_read(const DTVInfo *dtvi, const void *tsd_base, u32 module_id, u64 tls_offset, void **out) +{ + // DTV access is always indirect: TP+offset yields a pointer to the DTV array, + // which must be dereferenced before indexing by module ID. + const void *dtv_ptr; + if (bpf_probe_read_user(&dtv_ptr, sizeof(dtv_ptr), tsd_base + dtvi->offset)) { + goto err; + } + + // Index into the DTV to find this module's TLS block base address. + // DTV layout: [generation, module1_block, module2_block, ...] + // Entry size varies: 8 bytes (musl) or 16 bytes (glibc). + void *tls_block; + u64 dtv_entry_offset = (u64)module_id * dtvi->multiplier; + if (bpf_probe_read_user(&tls_block, sizeof(tls_block), (void *)(dtv_ptr + dtv_entry_offset))) { + goto err; + } + + // Read the actual TLS variable at tls_block + tls_offset + if (bpf_probe_read_user(out, sizeof(*out), tls_block + tls_offset)) { + goto err; + } + + DEBUG_PRINT( + "readDTV module %d, entry_offset 0x%llx, tls_offset 0x%llx", + module_id, + (unsigned long long)dtv_entry_offset, + (unsigned long long)tls_offset); + return 0; + +err: + DEBUG_PRINT( + "Failed to read TLS via DTV from 0x%lx for module %d", (unsigned long)dtv_ptr, module_id); + increment_metric(metricID_UnwindErrBadDTVRead); + return -1; +} + // tsd_get_base looks up the base address for TSD variables (TPBASE). static inline EBPF_INLINE int tsd_get_base(void **tsd_base) { diff --git a/support/ebpf/types.h b/support/ebpf/types.h index e035610f0..d5ea103f5 100644 --- a/support/ebpf/types.h +++ b/support/ebpf/types.h @@ -325,6 +325,9 @@ enum { // number of failed attempts to read a CME by exceeding max EP checks metricID_UnwindRubyErrCmeMaxEp, + // number of failures to read TLS variables via the DTV + metricID_UnwindErrBadDTVRead, + // // Metric IDs above are for counters (cumulative values) // @@ -355,6 +358,7 @@ typedef enum TracePrograms { PROG_UNWIND_DOTNET10, PROG_GO_LABELS, PROG_UNWIND_BEAM, + PROG_UNWIND_LUAJIT, NUM_TRACER_PROGS, } TracePrograms; @@ -389,15 +393,16 @@ typedef struct TSDInfo { } TSDInfo; // DTVInfo contains data needed to read Thread Local Storage (TLS) values, which -// are located using the Dynamic Thread Vector (DTV) +// are located using the Dynamic Thread Vector (DTV). +// DTV access is always indirect: TP+offset yields a pointer to the DTV array, +// which must be dereferenced before indexing by module ID. This is true for +// both glibc and musl (the DTV is a separately-allocated array, not inline +// in the thread control block). typedef struct DTVInfo { - // Offset is the offset of DTV from FS base (or from thread pointer) + // Offset is the offset of the DTV pointer from the thread pointer base. s16 offset; - // Multiplier is the size of each DTV entry in bytes - // Typically 8 bytes on 64bit musl and 16 bytes on 64bit glibc + // Multiplier is the size of each DTV entry in bytes. u8 multiplier; - // Indirect is 0 if DTV is at FS+offset, 1 if at [FS+0]+offset - u8 indirect; } DTVInfo; // DotnetProcInfo is a container for the data needed to build stack trace for a dotnet process. @@ -472,6 +477,13 @@ typedef struct RubyProcInfo { // Signed because static TLS offsets (local exec model) are negative on x86_64. s64 current_ec_tpbase_tls_offset; + // DTV-based TLS access for ruby_current_ec (fallback when TLSDESC unavailable) + DTVInfo dtv_info; + // Offset of ruby_current_ec within its module's TLS block + u64 current_ec_tls_offset; + // Runtime TLS module ID for libruby.so (from DTPMOD64 relocation, written by linker) + u32 tls_module_id; + // current_ctx_ptr holds the address of the symbol ruby_current_execution_context_ptr. u64 current_ctx_ptr; @@ -554,6 +566,11 @@ typedef union ApmSpanID { _Static_assert(sizeof(ApmSpanID) == 8, "unexpected trace ID size"); +typedef struct __attribute__((packed)) SpanTraceInfo { + ApmTraceID trace_id; + ApmSpanID span_id; +} SpanTraceInfo; + // Defines the format of the APM correlation TLS buffer. // // Specification: @@ -613,8 +630,9 @@ typedef struct Trace { // origin indicates the source of the trace. TraceOrigin origin; - // offtime stores the nanoseconds that the trace was off-cpu for. - u64 offtime; + // value stores context-specific data that was collected with the stack. + // e.g. time in nanoseconds for off-CPU traces + u64 value; // The frame data of the stack trace. Each frame is variable length. // Frame is currently 2-3 entries long. This array size limits the @@ -646,7 +664,7 @@ typedef struct UnwindState { // The per-CPU registers which are not unwound, but needed to be accessed // on leaf frames. #if defined(__x86_64__) - u64 rax, r9, r11, r13, r15; + u64 rax, rdi, r8, r9, r11, r13, r15; #elif defined(__aarch64__) u64 r20, r22, r28; #endif @@ -818,6 +836,13 @@ typedef struct PerCPURecord { GoMapBucket goMapBucket; // Scratch for Go 1.24 labels struct GoString labels[MAX_CUSTOM_LABELS * 2]; + // Signal frame registers for unwind_one_frame (avoids 272-byte stack alloc on arm64). + // Sized to match the kernel rt_sigframe register array for the target architecture. +#if defined(__x86_64__) + u64 rt_regs[18]; +#elif defined(__aarch64__) + u64 rt_regs[34]; +#endif }; // Mask to indicate which unwinders are complete u32 unwindersDone; @@ -853,19 +878,23 @@ typedef struct UnwindInfo { #define UNWIND_REG_LR 5 #define UNWIND_REG_X86_RAX 6 -#define UNWIND_REG_X86_R9 7 -#define UNWIND_REG_X86_R11 8 -#define UNWIND_REG_X86_R13 9 -#define UNWIND_REG_X86_R15 10 +#define UNWIND_REG_X86_RDI 7 +#define UNWIND_REG_X86_R8 8 +#define UNWIND_REG_X86_R9 9 +#define UNWIND_REG_X86_R11 10 +#define UNWIND_REG_X86_R13 11 +#define UNWIND_REG_X86_R15 12 // Flag to indicate a command (used inside Go stack delta generation only) -#define UNWIND_FLAG_COMMAND (1 << 0) +#define UNWIND_FLAG_COMMAND (1 << 0) // Flag to indicate that a full LR+FR frame is present on aarch64 -#define UNWIND_FLAG_FRAME (1 << 1) +#define UNWIND_FLAG_FRAME (1 << 1) // Flag to indicate that unwinding is valid on leaf frames only (uses untracked register) -#define UNWIND_FLAG_LEAF_ONLY (1 << 2) +#define UNWIND_FLAG_LEAF_ONLY (1 << 2) // Flag to indicate that the resolve CFA value should be dereferenced -#define UNWIND_FLAG_DEREF_CFA (1 << 3) +#define UNWIND_FLAG_DEREF_CFA (1 << 3) +// Flag to indicate that the return address is in a register +#define UNWIND_FLAG_REGISTER_RA (1 << 4) // If flags has UNWIND_FLAG_DEREF_CFA set, the lowest bits of 'param' are used // as second adder as post-deref operation. This contains the mask for that. @@ -945,6 +974,7 @@ typedef struct OffsetRange { typedef struct SystemAnalysis { u64 address; u32 pid; + s32 err; u8 code[128]; } SystemAnalysis; @@ -955,8 +985,7 @@ typedef struct Event { } Event; // Event types that notifications are sent for through event_send_trigger. -#define EVENT_TYPE_GENERIC_PID 1 -#define EVENT_TYPE_RELOAD_KALLSYMS 2 +#define EVENT_TYPE_GENERIC_PID 1 // PIDPage represents the key of the eBPF map pid_page_to_mapping_info. typedef struct PIDPage { diff --git a/support/ebpf/v8_tracer.ebpf.c b/support/ebpf/v8_tracer.ebpf.c index ee248a55a..61952dfad 100644 --- a/support/ebpf/v8_tracer.ebpf.c +++ b/support/ebpf/v8_tracer.ebpf.c @@ -14,7 +14,7 @@ #include "types.h" // The number of V8 frames to unwind per frame-unwinding eBPF program. -#define V8_FRAMES_PER_PROGRAM 8 +#define V8_FRAMES_PER_PROGRAM 60 // The maximum V8 frame length used in heuristic to validate FP #define V8_MAX_FRAME_LENGTH 8192 diff --git a/support/local-collector.sh b/support/local-collector.sh index 2e5bf3602..c4cc9451c 100755 --- a/support/local-collector.sh +++ b/support/local-collector.sh @@ -16,4 +16,6 @@ go list -m -u all | grep 'go\.opentelemetry\.io/collector' | while read -r line; LOCAL_PATH="$COLLECTOR_PATH/$REL_PATH" echo "Replacing $MODULE => $LOCAL_PATH" go mod edit -replace="$MODULE=$LOCAL_PATH" + # Also add replace directive to internal/tools/go.mod + go mod edit -modfile=internal/tools/go.mod -replace="$MODULE=$LOCAL_PATH" done diff --git a/support/types.go b/support/types.go index 63e45b493..b982a1130 100644 --- a/support/types.go +++ b/support/types.go @@ -22,6 +22,7 @@ const ( FrameMarkerPerl = 0x7 FrameMarkerV8 = 0x8 FrameMarkerDotnet = 0xa + FrameMarkerLuaJIT = 0xd FrameMarkerBEAM = 0xc FrameMarkerGo = 0xb ) @@ -45,6 +46,7 @@ const ( ProgUnwindDotnet10 = 0x9 ProgGoLabels = 0xa ProgUnwindBEAM = 0xb + ProgUnwindLuaJIT = 0xc ) const ( @@ -54,14 +56,13 @@ const ( ) const ( - EventTypeGenericPID = 0x1 - EventTypeReloadKallsyms = 0x2 + EventTypeGenericPID = 0x1 ) const UnwindInfoMaxEntries = 0x4000 const ( - MetricIDBeginCumulative = 0x68 + MetricIDBeginCumulative = 0x69 ) const ( @@ -138,10 +139,10 @@ type StackDeltaPageKey struct { Page uint64 } type SystemAnalysis struct { - Address uint64 - Pid uint32 - Code [128]uint8 - Pad_cgo_0 [4]byte + Address uint64 + Pid uint32 + Err int32 + Code [128]uint8 } type TSDInfo struct { Offset int16 @@ -151,7 +152,7 @@ type TSDInfo struct { type DTVInfo struct { Offset int16 Multiplier uint8 - Indirect uint8 + Pad_cgo_0 [1]byte } type Trace struct { Pid uint32 @@ -165,7 +166,7 @@ type Trace struct { Num_frames uint16 Num_kernel_frames uint16 Origin uint32 - Offtime uint64 + Value uint64 Frame_data [3072]uint64 } type UnwindInfo struct { @@ -281,6 +282,9 @@ type PyProcInfo struct { type RubyProcInfo struct { Version uint32 Current_ec_tpbase_tls_offset int64 + Dtv_info DTVInfo + Current_ec_tls_offset uint64 + Tls_module_id uint32 Current_ctx_ptr uint64 Has_objspace bool Vm_stack uint8 @@ -331,7 +335,7 @@ const ( sizeof_ApmIntProcInfo = 0x8 sizeof_DotnetProcInfo = 0x4 sizeof_PHPProcInfo = 0x18 - sizeof_RubyProcInfo = 0x30 + sizeof_RubyProcInfo = 0x48 ) const ( @@ -342,14 +346,18 @@ const ( UnwindRegFp uint8 = 0x4 UnwindRegLr uint8 = 0x5 UnwindRegX86RAX uint8 = 0x6 - UnwindRegX86R9 uint8 = 0x7 - UnwindRegX86R11 uint8 = 0x8 - UnwindRegX86R15 uint8 = 0xa + UnwindRegX86R9 uint8 = 0x9 + UnwindRegX86R11 uint8 = 0xa + UnwindRegX86R13 uint8 = 0xb + UnwindRegX86R15 uint8 = 0xc + UnwindRegX86RDI uint8 = 0x7 + UnwindRegX86R8 uint8 = 0x8 - UnwindFlagCommand uint8 = 0x1 - UnwindFlagFrame uint8 = 0x2 - UnwindFlagLeafOnly uint8 = 0x4 - UnwindFlagDerefCfa uint8 = 0x8 + UnwindFlagCommand uint8 = 0x1 + UnwindFlagFrame uint8 = 0x2 + UnwindFlagLeafOnly uint8 = 0x4 + UnwindFlagDerefCfa uint8 = 0x8 + UnwindFlagRegisterRA uint8 = 0x10 UnwindCommandInvalid int32 = 0x0 UnwindCommandStop int32 = 0x1 @@ -489,4 +497,5 @@ var MetricsTranslation = []metrics.MetricID{ 0x65: metrics.IDUnwindRubyErrReadSvar, 0x66: metrics.IDUnwindRubyErrReadRbasicFlags, 0x67: metrics.IDUnwindRubyErrCmeMaxEp, + 0x68: metrics.IDUnwindErrBadDTVRead, } diff --git a/support/types_def.go b/support/types_def.go index e8233cca0..3db048866 100644 --- a/support/types_def.go +++ b/support/types_def.go @@ -28,6 +28,7 @@ const ( FrameMarkerPerl = C.FRAME_MARKER_PERL FrameMarkerV8 = C.FRAME_MARKER_V8 FrameMarkerDotnet = C.FRAME_MARKER_DOTNET + FrameMarkerLuaJIT = C.FRAME_MARKER_LUAJIT FrameMarkerBEAM = C.FRAME_MARKER_BEAM FrameMarkerGo = C.FRAME_MARKER_GO ) @@ -51,6 +52,7 @@ const ( ProgUnwindDotnet10 = C.PROG_UNWIND_DOTNET10 ProgGoLabels = C.PROG_GO_LABELS ProgUnwindBEAM = C.PROG_UNWIND_BEAM + ProgUnwindLuaJIT = C.PROG_UNWIND_LUAJIT ) const ( @@ -60,8 +62,7 @@ const ( ) const ( - EventTypeGenericPID = C.EVENT_TYPE_GENERIC_PID - EventTypeReloadKallsyms = C.EVENT_TYPE_RELOAD_KALLSYMS + EventTypeGenericPID = C.EVENT_TYPE_GENERIC_PID ) const UnwindInfoMaxEntries = C.UNWIND_INFO_MAX_ENTRIES @@ -152,13 +153,17 @@ const ( UnwindRegX86RAX uint8 = C.UNWIND_REG_X86_RAX UnwindRegX86R9 uint8 = C.UNWIND_REG_X86_R9 UnwindRegX86R11 uint8 = C.UNWIND_REG_X86_R11 + UnwindRegX86R13 uint8 = C.UNWIND_REG_X86_R13 UnwindRegX86R15 uint8 = C.UNWIND_REG_X86_R15 + UnwindRegX86RDI uint8 = C.UNWIND_REG_X86_RDI + UnwindRegX86R8 uint8 = C.UNWIND_REG_X86_R8 // UnwindFlag values from the C header file - UnwindFlagCommand uint8 = C.UNWIND_FLAG_COMMAND - UnwindFlagFrame uint8 = C.UNWIND_FLAG_FRAME - UnwindFlagLeafOnly uint8 = C.UNWIND_FLAG_LEAF_ONLY - UnwindFlagDerefCfa uint8 = C.UNWIND_FLAG_DEREF_CFA + UnwindFlagCommand uint8 = C.UNWIND_FLAG_COMMAND + UnwindFlagFrame uint8 = C.UNWIND_FLAG_FRAME + UnwindFlagLeafOnly uint8 = C.UNWIND_FLAG_LEAF_ONLY + UnwindFlagDerefCfa uint8 = C.UNWIND_FLAG_DEREF_CFA + UnwindFlagRegisterRA uint8 = C.UNWIND_FLAG_REGISTER_RA // UnwindCommands from the C header file UnwindCommandInvalid int32 = C.UNWIND_COMMAND_INVALID @@ -302,4 +307,5 @@ var MetricsTranslation = []metrics.MetricID{ C.metricID_UnwindRubyErrReadSvar: metrics.IDUnwindRubyErrReadSvar, C.metricID_UnwindRubyErrReadRbasicFlags: metrics.IDUnwindRubyErrReadRbasicFlags, C.metricID_UnwindRubyErrCmeMaxEp: metrics.IDUnwindRubyErrCmeMaxEp, + C.metricID_UnwindErrBadDTVRead: metrics.IDUnwindErrBadDTVRead, } diff --git a/tools/coredump/analyze.go b/tools/coredump/analyze.go index b3c793ce5..a17a41263 100644 --- a/tools/coredump/analyze.go +++ b/tools/coredump/analyze.go @@ -111,7 +111,7 @@ func (cmd *analyzeCmd) exec(context.Context, []string) (err error) { } defer proc.Close() - threads, err := ExtractTraces(context.Background(), proc, cmd.debugEbpf, lwpFilter) + threads, err := ExtractTraces(context.Background(), proc, cmd.debugEbpf, lwpFilter, nil) if err != nil { return fmt.Errorf("failed to extract traces: %w", err) } diff --git a/tools/coredump/coredump.go b/tools/coredump/coredump.go index b4dd54754..d653935da 100644 --- a/tools/coredump/coredump.go +++ b/tools/coredump/coredump.go @@ -116,7 +116,7 @@ func (t *traceReporter) ReportTraceEvent(trace *libpf.Trace, meta *samples.Trace } func ExtractTraces(ctx context.Context, pr process.Process, debug bool, - lwpFilter libpf.Set[libpf.PID]) ([]ThreadInfo, error) { + lwpFilter libpf.Set[libpf.PID], faultAddresses map[uintptr]int) ([]ThreadInfo, error) { todo, cancel := context.WithCancel(ctx) defer cancel() @@ -159,7 +159,7 @@ func ExtractTraces(ctx context.Context, pr process.Process, debug bool, } // Interfaces for the managers - ebpfCtx := newEBPFContext(pr) + ebpfCtx := newEBPFContext(pr, faultAddresses) defer ebpfCtx.release() inverse_pac_mask := ^(pr.GetMachineData().CodePACMask) diff --git a/tools/coredump/coredump_test.go b/tools/coredump/coredump_test.go index c873e53bd..fb9163487 100644 --- a/tools/coredump/coredump_test.go +++ b/tools/coredump/coredump_test.go @@ -4,6 +4,7 @@ package main import ( + "strconv" "testing" "github.com/stretchr/testify/require" @@ -11,6 +12,25 @@ import ( "go.opentelemetry.io/ebpf-profiler/tools/coredump/modulestore" ) +// parseFaultAddresses converts the hex/decimal address strings from a test +// case JSON into the uintptr-keyed map consumed by the ebpfContext. The int +// values are hit counters initialized to 0; ExtractTraces will fail the test +// if any remain 0 after the unwind. ParseUint with base=0 honors a "0x" +// prefix, so both "0x7f12..." and decimal forms work. +func parseFaultAddresses(t *testing.T, raw []string) map[uintptr]int { + t.Helper() + if len(raw) == 0 { + return nil + } + out := make(map[uintptr]int, len(raw)) + for _, s := range raw { + v, err := strconv.ParseUint(s, 0, 64) + require.NoErrorf(t, err, "invalid fault-address %q", s) + out[uintptr(v)] = 0 + } + return out +} + func TestCoreDumps(t *testing.T) { cases, err := findTestCases(true) require.NoError(t, err) @@ -34,10 +54,23 @@ func TestCoreDumps(t *testing.T) { require.NoError(t, err) defer core.Close() - data, err := ExtractTraces(t.Context(), core, false, nil) + faults := parseFaultAddresses(t, testCase.FaultAddresses) + data, err := ExtractTraces(t.Context(), core, false, nil, faults) require.NoError(t, err) require.Equal(t, testCase.Threads, data) + + // Every fault address listed in the test case must have been + // visited at least once by bpf_probe_read_user_with_test_fault; + // otherwise the test isn't actually exercising the recovery path + // it claims to (e.g. a stale address that the unwinder no longer + // reads). The map is mutated in place by the helper, so we can + // just iterate the post-run state. + for addr, hits := range faults { + require.Greaterf(t, hits, 0, + "fault address 0x%x was never visited by "+ + "bpf_probe_read_user_with_test_fault", addr) + } }) } } diff --git a/tools/coredump/ebpfcontext.go b/tools/coredump/ebpfcontext.go index 911618df6..0c9fd58c1 100644 --- a/tools/coredump/ebpfcontext.go +++ b/tools/coredump/ebpfcontext.go @@ -57,6 +57,14 @@ type ebpfContext struct { // stackDeltaFileID is context variable for nested map lookups stackDeltaFileID C.u64 + + // faultAddresses maps user-space addresses on which + // bpf_probe_read_user_with_test_fault should pretend the kernel could not + // read (returns -1) to a hit counter. The presence of a key (regardless of + // value) is what triggers the fault; the int value records how many times + // the helper visited that address during the unwind so tests can assert + // every injected fault actually exercised the code path under test. + faultAddresses map[uintptr]int } // ebpfContextMap is global mapping of EBPFContext id (PIDandTGID) to the actual data. @@ -65,8 +73,11 @@ type ebpfContext struct { // passed directly to the C code). var ebpfContextMap = map[C.u64]*ebpfContext{} -// newEBPFContext creates new EBPF Context from given core dump image -func newEBPFContext(pr process.Process) *ebpfContext { +// newEBPFContext creates new EBPF Context from given core dump image. The +// faultAddresses map, if non-empty, instructs bpf_probe_read_user_with_test_fault +// to return -1 for those addresses; the int value of each entry is incremented +// each time the helper visits the address. +func newEBPFContext(pr process.Process, faultAddresses map[uintptr]int) *ebpfContext { pid := pr.PID() ctx := &ebpfContext{ trace: libpf.EbpfTrace{PID: pid}, @@ -78,6 +89,7 @@ func newEBPFContext(pr process.Process) *ebpfContext { maps: make(map[unsafe.Pointer]map[any]unsafe.Pointer), perCPURecord: C.malloc(C.sizeof_PerCPURecord), unwindInfoArray: C.malloc(C.sizeof_UnwindInfo * C.ulong(support.UnwindInfoMaxEntries)), + faultAddresses: faultAddresses, } ebpfContextMap[ctx.PIDandTGID] = ctx return ctx diff --git a/tools/coredump/ebpfhelpers.go b/tools/coredump/ebpfhelpers.go index 0ee5048b9..117fbef0f 100644 --- a/tools/coredump/ebpfhelpers.go +++ b/tools/coredump/ebpfhelpers.go @@ -55,6 +55,31 @@ func __bpf_probe_read_user(id C.u64, buf unsafe.Pointer, sz C.int, ptr unsafe.Po return 0 } +//export __bpf_probe_read_user_with_test_fault +func __bpf_probe_read_user_with_test_fault( + id C.u64, buf unsafe.Pointer, sz C.int, ptr unsafe.Pointer, +) C.long { + ctx := ebpfContextMap[id] + addr := uintptr(ptr) + // Trace every call so coredump test authors can grep the test output to + // pick a candidate address (e.g. the 192-byte read of a PyCodeObject) when + // constructing a fault-injection test case. + log.Debugf("bpf_probe_read_user_with_test_fault: sz=%d ptr=0x%x", int(sz), addr) + if _, ok := ctx.faultAddresses[addr]; ok { + // This log line stays at Info level so it's visible in CI when a + // fault-injection test actually exercises the recovery path. + log.Infof("bpf_probe_read_user_with_test_fault: injecting fault at 0x%x (sz=%d)", + addr, int(sz)) + ctx.faultAddresses[addr]++ + return -1 + } + dst := sliceBuffer(buf, sz) + if _, err := ctx.remoteMemory.ReadAt(dst, int64(addr)); err != nil { + return -1 + } + return 0 +} + // stackDeltaInnerMap is a special map returned to C code to indicate that // we are accessing one of nested maps in the exe_id_to_X_stack_deltas maps var stackDeltaInnerMap = C.malloc(1) diff --git a/tools/coredump/ebpfmaps.go b/tools/coredump/ebpfmaps.go index db9b83709..db5070665 100644 --- a/tools/coredump/ebpfmaps.go +++ b/tools/coredump/ebpfmaps.go @@ -234,9 +234,9 @@ func (emc *ebpfMapsCoredump) UpdatePidPageMappingInfo(pid libpf.PID, prefix lpm. host.FileID(fileID), bias) } -func (emc *ebpfMapsCoredump) DeletePidPageMappingInfo(pid libpf.PID, prefixes []lpm.Prefix) (int, +func (emc *ebpfMapsCoredump) DeletePidPageMappingInfo(pid libpf.PID, prefixes []lpm.Prefix) (uint64, error) { - var deleted int + var deleted uint64 for _, prefix := range prefixes { if err := emc.DeletePidInterpreterMapping(pid, prefix); err != nil { return deleted, err diff --git a/tools/coredump/json.go b/tools/coredump/json.go index f0c5fd5cc..0addff0e1 100644 --- a/tools/coredump/json.go +++ b/tools/coredump/json.go @@ -21,6 +21,11 @@ type CoredumpTestCase struct { Skip string `json:"skip,omitempty"` Threads []ThreadInfo `json:"threads"` Modules []ModuleInfo `json:"modules"` + // FaultAddresses is an optional list of user-space addresses (hex strings, + // e.g. "0x7f1234567000") at which the test harness should make + // bpf_probe_read_user_with_test_fault return -1, simulating a BPF read + // failure. Used to exercise recovery paths. + FaultAddresses []string `json:"fault-addresses,omitempty"` } // ModuleInfo stores information about a module that was loaded when the coredump was created. diff --git a/tools/coredump/modulestore/util.go b/tools/coredump/modulestore/util.go index 08c43ba07..11feb7e71 100644 --- a/tools/coredump/modulestore/util.go +++ b/tools/coredump/modulestore/util.go @@ -40,6 +40,10 @@ func getS3ObjectList(client *s3.Client, bucket, prefix string, return nil, errors.New("too many matching items in bucket") } + // Passing a nil continuation token to ListObjectsV2 will restart the operation and lead to an infinite loop + if resp.ContinuationToken == nil || *resp.ContinuationToken == "" { + break + } contToken = resp.ContinuationToken } diff --git a/tools/coredump/new.go b/tools/coredump/new.go index 73b382d82..9be0b7b47 100644 --- a/tools/coredump/new.go +++ b/tools/coredump/new.go @@ -60,7 +60,7 @@ func newTrackedCoredump(corePath, filePrefix string) (*trackedCoredump, error) { }, nil } -func (tc *trackedCoredump) GetMappingFileLastModified(_ *process.Mapping) int64 { +func (tc *trackedCoredump) GetMappingFileLastModified(_ *process.RawMapping) int64 { return 0 } @@ -71,9 +71,9 @@ func (tc *trackedCoredump) warnMissing(fileName string) { } } -func (tc *trackedCoredump) CalculateMappingFileID(m *process.Mapping) (libpf.FileID, error) { +func (tc *trackedCoredump) CalculateMappingFileID(m *process.RawMapping) (libpf.FileID, error) { if !m.IsVDSO() && !m.IsAnonymous() { - file := m.Path.String() + file := m.Path fid, err := libpf.FileIDFromExecutableFile(path.Join(tc.prefix, file)) if err == nil { tc.seen[file] = libpf.Void{} @@ -84,9 +84,9 @@ func (tc *trackedCoredump) CalculateMappingFileID(m *process.Mapping) (libpf.Fil return tc.CoredumpProcess.CalculateMappingFileID(m) } -func (tc *trackedCoredump) OpenMappingFile(m *process.Mapping) (process.ReadAtCloser, error) { +func (tc *trackedCoredump) OpenMappingFile(m *process.RawMapping) (process.ReadAtCloser, error) { if !m.IsVDSO() && !m.IsAnonymous() { - file := m.Path.String() + file := m.Path rac, err := os.Open(path.Join(tc.prefix, file)) if err == nil { tc.seen[file] = libpf.Void{} @@ -98,7 +98,7 @@ func (tc *trackedCoredump) OpenMappingFile(m *process.Mapping) (process.ReadAtCl } func (tc *trackedCoredump) OpenELF(fileName string) (*pfelf.File, error) { - if fileName != process.VdsoPathName.String() { + if fileName != process.VdsoPathName { f, err := pfelf.Open(path.Join(tc.prefix, fileName)) if err == nil { tc.seen[fileName] = libpf.Void{} @@ -169,7 +169,7 @@ func (cmd *newCmd) exec(context.Context, []string) (err error) { testCase := &CoredumpTestCase{} - testCase.Threads, err = ExtractTraces(context.Background(), core, cmd.debugEbpf, nil) + testCase.Threads, err = ExtractTraces(context.Background(), core, cmd.debugEbpf, nil, nil) if err != nil { return fmt.Errorf("failed to extract traces: %w", err) } diff --git a/tools/coredump/rebase.go b/tools/coredump/rebase.go index 8b024e2ef..d88005ea7 100644 --- a/tools/coredump/rebase.go +++ b/tools/coredump/rebase.go @@ -61,7 +61,7 @@ func (cmd *rebaseCmd) exec(context.Context, []string) (err error) { return fmt.Errorf("failed to open coredump: %w", err) } - testCase.Threads, err = ExtractTraces(context.Background(), core, false, nil) + testCase.Threads, err = ExtractTraces(context.Background(), core, false, nil, nil) _ = core.Close() if err != nil { return fmt.Errorf("failed to extract traces: %w", err) diff --git a/tools/coredump/storecoredump.go b/tools/coredump/storecoredump.go index 954c700a1..b1ecef55d 100644 --- a/tools/coredump/storecoredump.go +++ b/tools/coredump/storecoredump.go @@ -45,8 +45,8 @@ func (scd *StoreCoredump) openFile(path string) (process.ReadAtCloser, error) { return file, nil } -func (scd *StoreCoredump) OpenMappingFile(m *process.Mapping) (process.ReadAtCloser, error) { - return scd.openFile(m.Path.String()) +func (scd *StoreCoredump) OpenMappingFile(m *process.RawMapping) (process.ReadAtCloser, error) { + return scd.openFile(m.Path) } func (scd *StoreCoredump) OpenELF(path string) (*pfelf.File, error) { diff --git a/tools/coredump/testdata/amd64/dotnet10-alpine3.23.json b/tools/coredump/testdata/amd64/dotnet10-alpine3.23.json index c8e0f4105..03172a019 100644 --- a/tools/coredump/testdata/amd64/dotnet10-alpine3.23.json +++ b/tools/coredump/testdata/amd64/dotnet10-alpine3.23.json @@ -49,7 +49,7 @@ "ld-musl-x86_64.so.1+0x6799f", "ld-musl-x86_64.so.1+0x6731a", "ld-musl-x86_64.so.1+0x6757a", - "" + "ld-musl-x86_64.so.1+0x68ec0" ] }, { diff --git a/tools/coredump/testdata/amd64/python3.14-faulted-codeobject.json b/tools/coredump/testdata/amd64/python3.14-faulted-codeobject.json new file mode 100644 index 000000000..886a316a2 --- /dev/null +++ b/tools/coredump/testdata/amd64/python3.14-faulted-codeobject.json @@ -0,0 +1,81 @@ +{ + "coredump-ref": "a69bc3dea27b197295177ab1645783ddf27e682fafc9102ef7723b05c528285c", + "fault-addresses": [ + "0x7fb421b7cb70", + "0x7fb421b51c30" + ], + "threads": [ + { + "lwp": 28509, + "frames": [ + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "recur_fibo+4 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:7", + "+9 in /tmp/opentelemetry-ebpf-profiler/tools/coredump/testsources/python/fib.py:10", + "?+0x0", + "python3.14+0x5454c6", + "python3.14+0x53ffa0", + "python3.14+0x6b741e", + "python3.14+0x6b3dcf", + "python3.14+0x6b377d", + "python3.14+0x6b3271", + "python3.14+0x6b0552", + "python3.14+0x65ea68", + "libc.so.6+0x29f74", + "libc.so.6+0x2a026", + "python3.14+0x65de70" + ] + } + ], + "modules": [ + { + "ref": "85590dd58edf5445e18bc7193e5ebc01ac5841f1ae187e97705a662e90c6421e", + "local-path": "/usr/lib/x86_64-linux-gnu/libz.so.1.3.1" + }, + { + "ref": "e9a55da498abf2f190abd96115c7b9381d51968729b35e9d351e6a16cc951e5c", + "local-path": "/usr/lib/x86_64-linux-gnu/libm.so.6" + }, + { + "ref": "f5ffb9a6143905c445c980b7430ea94ad800b3c15dc83d5ec7667c6807f595dd", + "local-path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2" + }, + { + "ref": "af6ab77528a4732280b9e158c987893a492ae315929277493fb60f0b4d381d8c", + "local-path": "/usr/bin/python3.14" + }, + { + "ref": "7d771b3a3182ca9193afafa461e099af06ec929b6171bb1c20920c11af3e5850", + "local-path": "/usr/lib/x86_64-linux-gnu/libc.so.6" + }, + { + "ref": "01ed980b9420653bfa242cdddcfed0b59c5dc066c79d95b8637a52d0135a64f1", + "local-path": "/usr/lib/x86_64-linux-gnu/libexpat.so.1.11.1" + } + ] +} diff --git a/tools/coredump/testdata/amd64/ruby-3.4.7-dtv-loop.json b/tools/coredump/testdata/amd64/ruby-3.4.7-dtv-loop.json new file mode 100644 index 000000000..f378bd3b5 --- /dev/null +++ b/tools/coredump/testdata/amd64/ruby-3.4.7-dtv-loop.json @@ -0,0 +1,94 @@ +{ + "coredump-ref": "04198656d80942275b90395583ac2d04160a2493375c7befd5e7a0f39bcb3dac", + "threads": [ + { + "lwp": 157954, + "frames": [ + "libruby.so.3.4.7+0x312edf", + "Object#is_prime+0 in /home/dalehamel/opentelemetry-ebpf-profiler/tools/coredump/testsources/ruby/loop.rb:15", + "libruby.so.3.4.7+0x316278", + "libruby.so.3.4.7+0x31b0b7", + "libruby.so.3.4.7+0x225a4d", + "libruby.so.3.4.7+0x2fc1eb", + "libruby.so.3.4.7+0x302c2e", + "libruby.so.3.4.7+0x3103ad", + "Range#each+0 in :0", + "Object#is_prime+0 in /home/dalehamel/opentelemetry-ebpf-profiler/tools/coredump/testsources/ruby/loop.rb:14", + "Object#sum_of_primes+0 in /home/dalehamel/opentelemetry-ebpf-profiler/tools/coredump/testsources/ruby/loop.rb:24", + "block (2 levels) in
+0 in /home/dalehamel/opentelemetry-ebpf-profiler/tools/coredump/testsources/ruby/loop.rb:34", + "libruby.so.3.4.7+0x3162f1", + "libruby.so.3.4.7+0x31b0b7", + "libruby.so.3.4.7+0x22593d", + "libruby.so.3.4.7+0x2fad84", + "libruby.so.3.4.7+0x302c2e", + "libruby.so.3.4.7+0x3103ad", + "Range#each+0 in :0", + "block in
+0 in /home/dalehamel/opentelemetry-ebpf-profiler/tools/coredump/testsources/ruby/loop.rb:33", + "Kernel#loop+0 in :168", + "
+0 in /home/dalehamel/opentelemetry-ebpf-profiler/tools/coredump/testsources/ruby/loop.rb:32", + "libruby.so.3.4.7+0x316278", + "libruby.so.3.4.7+0x11eb28", + "libruby.so.3.4.7+0x1225ba", + "ruby+0x1111", + "libc.so.6+0x27249", + "libc.so.6+0x27304", + "ruby+0x1150" + ] + }, + { + "lwp": 157956, + "frames": [ + "libc.so.6+0x108f26", + "libruby.so.3.4.7+0x2bbdf4", + "libc.so.6+0x891f4", + "libc.so.6+0x1098db" + ] + } + ], + "modules": [ + { + "ref": "de70efd1ae64a21ddf67e16d6974e15e915775bb49dcf9f54b29d28b421f9cf7", + "local-path": "/home/dalehamel/.rubies/ruby-3.4.7-shared/lib/libruby.so.3.4.7" + }, + { + "ref": "7e2a72b4c4b38c61e6962de6e3f4a5e9ae692e732c68deead10a7ce2135a7f68", + "local-path": "/usr/lib/x86_64-linux-gnu/libz.so.1.2.13" + }, + { + "ref": "8e08758f96765c14bf25e66de3c5e18c858a4554c6264f5c11d532695144728b", + "local-path": "/home/dalehamel/.rubies/ruby-3.4.7-shared/lib/ruby/3.4.0/x86_64-linux/monitor.so" + }, + { + "ref": "91c9e1e922767e7cacd8c432f83774b31eb8b90253bd11e85f6734617f7d7046", + "local-path": "/home/dalehamel/.rubies/ruby-3.4.7-shared/lib/ruby/3.4.0/x86_64-linux/enc/trans/transdb.so" + }, + { + "ref": "5db18e8a8894ef4746eb8230855b638a5e52e782b2f10deede5f1dad846178bb", + "local-path": "/usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0" + }, + { + "ref": "7376c9af0afd6e7698a64ee19de3c8a0199418664974384c70435a51c7ff7f3f", + "local-path": "/usr/lib/x86_64-linux-gnu/libgmp.so.10.4.1" + }, + { + "ref": "19914ee04698ae539d05ec4562ee6bb617550f269d9fdca5db576216a4ae2a5f", + "local-path": "/home/dalehamel/.rubies/ruby-3.4.7-shared/lib/ruby/3.4.0/x86_64-linux/enc/encdb.so" + }, + { + "ref": "593bb1d5355658e645f36e6b1f49832691b24e177209765914e4cce51499dbb4", + "local-path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2" + }, + { + "ref": "b7013a7dfb98df74b4112f83673416758fab092ffaf24b774733a877768b7549", + "local-path": "/home/dalehamel/.rubies/ruby-3.4.7-shared/bin/ruby" + }, + { + "ref": "bff8750fe719e6000791b88b11747dce8772c37118d0b2348044b70819d13835", + "local-path": "/usr/lib/x86_64-linux-gnu/libc.so.6" + }, + { + "ref": "7f2ca87f652f56b094462474b076749e90e689d0ecb9cb63c7679820b271b4e7", + "local-path": "/usr/lib/x86_64-linux-gnu/libm.so.6" + } + ] +} diff --git a/tools/coredump/testdata/amd64/ruby-3.4.7-musl-dtv-loop.json b/tools/coredump/testdata/amd64/ruby-3.4.7-musl-dtv-loop.json new file mode 100644 index 000000000..b73c9abee --- /dev/null +++ b/tools/coredump/testdata/amd64/ruby-3.4.7-musl-dtv-loop.json @@ -0,0 +1,86 @@ +{ + "coredump-ref": "79af7ad4c7427e9919720c2bb6ff6ba06ca9ff2a052a5e1c58dad05cdbf58518", + "threads": [ + { + "lwp": 1, + "frames": [ + "libruby.so.3.4.7+0x339606", + "libruby.so.3.4.7+0x33df39", + "libruby.so.3.4.7+0x342c27", + "libruby.so.3.4.7+0x23c7a8", + "libruby.so.3.4.7+0x3217b0", + "libruby.so.3.4.7+0x328d64", + "libruby.so.3.4.7+0x3393c5", + "Range#each+0 in :0", + "Object#is_prime+0 in /src/tools/coredump/testsources/ruby/loop.rb:14", + "Object#sum_of_primes+0 in /src/tools/coredump/testsources/ruby/loop.rb:24", + "block (2 levels) in
+0 in /src/tools/coredump/testsources/ruby/loop.rb:34", + "libruby.so.3.4.7+0x33e009", + "libruby.so.3.4.7+0x342c27", + "libruby.so.3.4.7+0x23c9ed", + "libruby.so.3.4.7+0x32034b", + "libruby.so.3.4.7+0x328d64", + "libruby.so.3.4.7+0x3393c5", + "Range#each+0 in :0", + "block in
+0 in /src/tools/coredump/testsources/ruby/loop.rb:33", + "Kernel#loop+0 in :168", + "
+0 in /src/tools/coredump/testsources/ruby/loop.rb:32", + "libruby.so.3.4.7+0x33df39", + "libruby.so.3.4.7+0x128ea8", + "libruby.so.3.4.7+0x12f2fa", + "ruby+0x10ff", + "ld-musl-x86_64.so.1+0x41a2f", + "ruby+0x1131" + ] + }, + { + "lwp": 8, + "frames": [ + "ld-musl-x86_64.so.1+0x68ef4", + "ld-musl-x86_64.so.1+0x66867", + "ld-musl-x86_64.so.1+0x4396b", + "libruby.so.3.4.7+0x2dd9e3", + "ld-musl-x86_64.so.1+0x67572", + "ld-musl-x86_64.so.1+0x68ec0" + ] + } + ], + "modules": [ + { + "ref": "0ea959387b385d63b7e8730cd4fc7e7f94ee032a61aacabc81e1df3ce4a9344e", + "local-path": "/lib/ld-musl-x86_64.so.1" + }, + { + "ref": "1374a685840d3b078875c64934b6a527012761d703e945fbd2541390f1403582", + "local-path": "/opt/ruby/bin/ruby" + }, + { + "ref": "4aa55d0d1f1fa458a84e197e5e70a2f78b803ee4044d2c2e127f2ff77785a459", + "local-path": "/opt/ruby/lib/ruby/3.4.0/x86_64-linux-musl/monitor.so" + }, + { + "ref": "60997f0db81cadd7cd12e787f3cc7de3c1e0946bc9bf28160c10199cceb59fef", + "local-path": "/usr/lib/libz.so.1.3.2" + }, + { + "ref": "19e4f8c57e95685e71690858b8132cb4d7a656a0548639996d148905fe5a40a5", + "local-path": "/opt/ruby/lib/libruby.so.3.4.7" + }, + { + "ref": "6b80d52dbcec730bec43441db8e33aba187bfa96bcd9afd052aa9177d051ee46", + "local-path": "/usr/lib/debug/lib/ld-musl-x86_64.so.1.debug" + }, + { + "ref": "2892d049d8f445b6d93c1acf365192a6620cf36b066c7c95129aa9d03f4a3ab0", + "local-path": "/usr/lib/libgmp.so.10.5.0" + }, + { + "ref": "9c0f20450e3696998548e0c037139631d39174835416a97c750f30f556ff1bd6", + "local-path": "/opt/ruby/lib/ruby/3.4.0/x86_64-linux-musl/enc/trans/transdb.so" + }, + { + "ref": "09d6781413d3bde58dc0961848052a2ce7342845db1105578477cdf8063996c6", + "local-path": "/opt/ruby/lib/ruby/3.4.0/x86_64-linux-musl/enc/encdb.so" + } + ] +} diff --git a/tools/coredump/testdata/amd64/vfork-unwind.json b/tools/coredump/testdata/amd64/vfork-unwind.json new file mode 100644 index 000000000..271b23f0e --- /dev/null +++ b/tools/coredump/testdata/amd64/vfork-unwind.json @@ -0,0 +1,33 @@ +{ + "coredump-ref": "8f5e743313c1d20f0636ae821e7f4d2fb3b720b2956711edca5d45bf48a13a82", + "threads": [ + { + "lwp": 95340, + "frames": [ + "libc.so.6+0xeca7a", + "libc.so.6+0xf9a26", + "libc.so.6+0x10ec92", + "vfork+0x12a1", + "vfork+0x12eb", + "vfork+0x114e", + "libc.so.6+0x2a1c9", + "libc.so.6+0x2a28a", + "vfork+0x1184" + ] + } + ], + "modules": [ + { + "ref": "bb3ea285bd3612ccf0642d06b89783e2d73ce7d4db33f88f62235cf8a61dc771", + "local-path": "/home/hanshal101/mg/opentelemetry-ebpf-profiler/vfork" + }, + { + "ref": "d8db8739a1633c972cec6a4fe0566bdcec6fd088f98723492ab0361f66238f75", + "local-path": "/usr/lib/x86_64-linux-gnu/libc.so.6" + }, + { + "ref": "1cd555ac46b7887edeaf3c42aac5408c8135e52f6b37870da2cf82d5fe14e829", + "local-path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2" + } + ] +} diff --git a/tools/coredump/testdata/arm64/java-17.ShaShenanigans.StubRoutines.sha256_implCompressMB.sha256h.json b/tools/coredump/testdata/arm64/java-17.ShaShenanigans.StubRoutines.sha256_implCompressMB.sha256h.json index 280f569b1..7da83f55f 100644 --- a/tools/coredump/testdata/arm64/java-17.ShaShenanigans.StubRoutines.sha256_implCompressMB.sha256h.json +++ b/tools/coredump/testdata/arm64/java-17.ShaShenanigans.StubRoutines.sha256_implCompressMB.sha256h.json @@ -40,7 +40,10 @@ "libjvm.so+0xbfe27b", "libjvm.so+0xe59ff7", "libjvm.so+0xe5a08f", - "" + "libjvm.so+0xdc3dcb", + "libjvm.so+0xb56fbb", + "libc.so.6+0x7edd7", + "libc.so.6+0xe7e5b" ] }, { @@ -66,7 +69,10 @@ "libjvm.so+0xbfe27b", "libjvm.so+0xe59ff7", "libjvm.so+0xe5a08f", - "" + "libjvm.so+0xdc3dcb", + "libjvm.so+0xb56fbb", + "libc.so.6+0x7edd7", + "libc.so.6+0xe7e5b" ] }, { diff --git a/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.add-sp-sp.377026.json b/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.add-sp-sp.377026.json index f74cda937..ee09766fe 100644 --- a/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.add-sp-sp.377026.json +++ b/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.add-sp-sp.377026.json @@ -121,7 +121,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { @@ -148,7 +151,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { diff --git a/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.ldp-x29-x30.376761.json b/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.ldp-x29-x30.376761.json index 638dd7552..f184daa81 100644 --- a/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.ldp-x29-x30.376761.json +++ b/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.ldp-x29-x30.376761.json @@ -55,7 +55,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { @@ -262,7 +265,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { diff --git a/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.ret.377192.json b/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.ret.377192.json index 2ed5dbbd4..2781725c7 100644 --- a/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.ret.377192.json +++ b/tools/coredump/testdata/arm64/java.PrologueEpilogue.epi.ret.377192.json @@ -62,7 +62,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { @@ -173,7 +176,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { diff --git a/tools/coredump/testdata/arm64/java.PrologueEpilogue.stp-fp-lr.json b/tools/coredump/testdata/arm64/java.PrologueEpilogue.stp-fp-lr.json index eedcad390..0e43c3626 100644 --- a/tools/coredump/testdata/arm64/java.PrologueEpilogue.stp-fp-lr.json +++ b/tools/coredump/testdata/arm64/java.PrologueEpilogue.stp-fp-lr.json @@ -63,7 +63,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { @@ -116,7 +119,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { diff --git a/tools/coredump/testdata/arm64/java.PrologueEpilogue.sub-sp-sp.json b/tools/coredump/testdata/arm64/java.PrologueEpilogue.sub-sp-sp.json index 493d67905..c95240b56 100644 --- a/tools/coredump/testdata/arm64/java.PrologueEpilogue.sub-sp-sp.json +++ b/tools/coredump/testdata/arm64/java.PrologueEpilogue.sub-sp-sp.json @@ -26,7 +26,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { @@ -156,7 +159,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { diff --git a/tools/coredump/testdata/arm64/java.VdsoPressure.osJavaTimeNanos.649996.json b/tools/coredump/testdata/arm64/java.VdsoPressure.osJavaTimeNanos.649996.json index 9fd1d2fd5..f8709f910 100644 --- a/tools/coredump/testdata/arm64/java.VdsoPressure.osJavaTimeNanos.649996.json +++ b/tools/coredump/testdata/arm64/java.VdsoPressure.osJavaTimeNanos.649996.json @@ -127,7 +127,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { @@ -286,7 +289,10 @@ "libjvm.so+0xc09c1b", "libjvm.so+0xe597f7", "libjvm.so+0xe5988f", - "" + "libjvm.so+0xdc5633", + "libjvm.so+0xb63fbb", + "libpthread-2.33.so+0x6f3b", + "libc-2.33.so+0xd2cdb" ] }, { diff --git a/tools/coredump/testdata/arm64/ruby-3.3.9-loop-with-ractors.json b/tools/coredump/testdata/arm64/ruby-3.3.9-loop-with-ractors.json index da94fd706..2bf952133 100644 --- a/tools/coredump/testdata/arm64/ruby-3.3.9-loop-with-ractors.json +++ b/tools/coredump/testdata/arm64/ruby-3.3.9-loop-with-ractors.json @@ -49,7 +49,7 @@ "libruby.so.3.3.9+0x2ac5cf", "libruby.so.3.3.9+0x2aca0b", "libruby.so.3.3.9+0x2ac8e7", - "" + "" ] }, { diff --git a/tools/coredump/testdata/arm64/ruby-3.4.5-loop-with-ractors.json b/tools/coredump/testdata/arm64/ruby-3.4.5-loop-with-ractors.json index 21fc2919f..f7b27f3eb 100644 --- a/tools/coredump/testdata/arm64/ruby-3.4.5-loop-with-ractors.json +++ b/tools/coredump/testdata/arm64/ruby-3.4.5-loop-with-ractors.json @@ -53,7 +53,7 @@ "libruby.so.3.4.5+0x2ce9f7", "libruby.so.3.4.5+0x2cefbf", "libruby.so.3.4.5+0x2cee9f", - "" + "" ] }, { diff --git a/tools/coredump/testdata/arm64/ruby-3.5.0-loop-with-ractors.json b/tools/coredump/testdata/arm64/ruby-3.5.0-loop-with-ractors.json index 3d78bc8ea..073508ac8 100644 --- a/tools/coredump/testdata/arm64/ruby-3.5.0-loop-with-ractors.json +++ b/tools/coredump/testdata/arm64/ruby-3.5.0-loop-with-ractors.json @@ -50,7 +50,7 @@ "libruby.so.3.5.0+0x2c6a47", "libruby.so.3.5.0+0x2c700b", "libruby.so.3.5.0+0x2c6eeb", - "" + "" ] }, { diff --git a/tools/coredump/testsources/c/vfork.c b/tools/coredump/testsources/c/vfork.c new file mode 100644 index 000000000..46707bed9 --- /dev/null +++ b/tools/coredump/testsources/c/vfork.c @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Example application that uses vfork() to test unwinding from the child +// process. +// +// cc -O2 -g -o vfork vfork.c + +#include +#include +#include +#include +#include +#include + +void child_func() { + // Allow any process to ptrace us (needed for gcore in some environments) + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); + + printf("Child: PID %d, Parent PID %d. Waiting for signal...\n", getpid(), + getppid()); + // Wait for gcore to capture us. + while (1) { + sleep(1); + } +} + +void parent_func() { + pid_t pid = vfork(); + if (pid == 0) { + // Child process + child_func(); + _exit(0); + } else if (pid > 0) { + // Parent process + // Note: Parent is BLOCKED here until child _exits or execs. + // So if child loops, parent stays here. + printf("Parent: Child PID %d finished\n", pid); + } else { + perror("vfork"); + } +} + +int main() { + parent_func(); + return 0; +} diff --git a/tools/probe-ctrl/go.mod b/tools/probe-ctrl/go.mod index 4a2ff937e..5326eeb13 100644 --- a/tools/probe-ctrl/go.mod +++ b/tools/probe-ctrl/go.mod @@ -11,7 +11,7 @@ replace go.opentelemetry.io/ebpf-profiler => ../../ require ( github.com/cilium/ebpf v0.21.0 - go.opentelemetry.io/ebpf-profiler v0.0.202610 + go.opentelemetry.io/ebpf-profiler v0.0.202618 ) require ( @@ -21,7 +21,7 @@ require ( github.com/elastic/go-perf v0.0.0-20260224073651-af0ee0c731b7 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/go-version v1.8.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect @@ -32,27 +32,28 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/zeebo/xxh3 v1.1.0 // indirect - go.opentelemetry.io/collector/consumer v1.53.0 // indirect - go.opentelemetry.io/collector/consumer/xconsumer v0.147.0 // indirect - go.opentelemetry.io/collector/featuregate v1.53.0 // indirect - go.opentelemetry.io/collector/pdata v1.53.0 // indirect - go.opentelemetry.io/collector/pdata/pprofile v0.147.0 // indirect - go.opentelemetry.io/otel v1.42.0 // indirect - go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/collector/consumer v1.57.0 // indirect + go.opentelemetry.io/collector/consumer/xconsumer v0.151.0 // indirect + go.opentelemetry.io/collector/featuregate v1.57.0 // indirect + go.opentelemetry.io/collector/pdata v1.57.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.151.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/arch v0.25.0 // indirect - golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect + golang.org/x/arch v0.26.0 // indirect + golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect - golang.org/x/mod v0.33.0 // indirect - golang.org/x/net v0.51.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect - golang.org/x/text v0.34.0 // indirect - golang.org/x/tools v0.42.0 // indirect - golang.org/x/vuln v1.1.4 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.79.2 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/telemetry v0.0.0-20260421165255-392afab6f40e // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/tools v0.44.0 // indirect + golang.org/x/vuln v1.3.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/grpc v1.81.0 // indirect google.golang.org/protobuf v1.36.11 // indirect honnef.co/go/tools v0.7.0 // indirect ) diff --git a/tools/probe-ctrl/go.sum b/tools/probe-ctrl/go.sum index 5cb23d290..bf4b113d5 100644 --- a/tools/probe-ctrl/go.sum +++ b/tools/probe-ctrl/go.sum @@ -33,8 +33,8 @@ github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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 v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= @@ -67,8 +67,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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/open-telemetry/sig-profiling/tools/profcheck v0.0.0-20260303084341-52f633d434c9 h1:3NStK3r8FVhXbU0qkVz/DpPQlaoLLgLHJOAMKyDX4WM= -github.com/open-telemetry/sig-profiling/tools/profcheck v0.0.0-20260303084341-52f633d434c9/go.mod h1:KRO+Rec0+KycN1CrIP/6Pu0xOraPhbahCbL36i8FkfM= +github.com/open-telemetry/sig-profiling/profcheck v0.0.0-20260504053944-5ab2ab77ce06 h1:G6x0sl7jhDrYYSkX8Gj6KyAeoqU78oQfGD0v8qWcVDQ= +github.com/open-telemetry/sig-profiling/profcheck v0.0.0-20260504053944-5ab2ab77ce06/go.mod h1:WPdgk1BinVdvYdkbt1KIkgDw6qUiK8CnGq+ftaJ41Ns= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -84,57 +84,57 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/collector/consumer v1.53.0 h1:Gyy80dX5r1Lv9lvQk8XFtUkWs1eniicOzzCQBejLseg= -go.opentelemetry.io/collector/consumer v1.53.0/go.mod h1:f5U6ibd+XpC5eOSeEYhERAQJ2a5bp1d2RzW3MFddMDM= -go.opentelemetry.io/collector/consumer/xconsumer v0.147.0 h1:XJVQc2dYyalaFXMTa4/RE+aweQTiBpw1edfwdCIJSxw= -go.opentelemetry.io/collector/consumer/xconsumer v0.147.0/go.mod h1:mtwh1VsUoGjxwdmXEzjbswH7KAGByJNCIMHmhqwXeK0= -go.opentelemetry.io/collector/featuregate v1.53.0 h1:cgjXdtl7jezWxq6V0eohe/JqjY4PBotZGb5+bTR2OJw= -go.opentelemetry.io/collector/featuregate v1.53.0/go.mod h1:PS7zY/zaCb28EqciePVwRHVhc3oKortTFXsi3I6ee4g= -go.opentelemetry.io/collector/internal/testutil v0.147.0 h1:DFlRxBRp23/sZnpTITK25yqe0d56yNvK+63IaWc6OsU= -go.opentelemetry.io/collector/internal/testutil v0.147.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= -go.opentelemetry.io/collector/pdata v1.53.0 h1:DlYDbRwammEZaxDZHINx5v0n8SEOVNniPbi6FRTlVkA= -go.opentelemetry.io/collector/pdata v1.53.0/go.mod h1:LRSYGNjKXaUrZEwZv3Yl+8/zV2HmRGKXW62zB2bysms= -go.opentelemetry.io/collector/pdata/pprofile v0.147.0 h1:yQS3RBvcvRcy9N7AnJvsxmse0AxJcRqBZfwMA22xBA8= -go.opentelemetry.io/collector/pdata/pprofile v0.147.0/go.mod h1:pm9mUqHNpT1SaCkxILu4FW1BvMAelh7EKhpSKe2KJIQ= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/collector/consumer v1.57.0 h1:jyDh4GkYPuIXNB0UJIh33NAzZoTCVNkwS+XWdlI08P8= +go.opentelemetry.io/collector/consumer v1.57.0/go.mod h1:tJKbog9Xw/8y66aWd/C+21BMuQkOWn/lF4bzJDRC9OM= +go.opentelemetry.io/collector/consumer/xconsumer v0.151.0 h1:eKIYxuPBEIrjZMAkyKBUWrlpHAE9OgxXBjq7PMSeXkE= +go.opentelemetry.io/collector/consumer/xconsumer v0.151.0/go.mod h1:9K97TkCN7XYfwKzPzktozrWc3Qw/4A1T4XgMn9TnG0c= +go.opentelemetry.io/collector/featuregate v1.57.0 h1:KPDSUKYn6MHwgyGRSGPPcW/G96HH93pxuvvPwM+R8nY= +go.opentelemetry.io/collector/featuregate v1.57.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= +go.opentelemetry.io/collector/internal/testutil v0.151.0 h1:CFjDItLuqzblItOsnK6IPSdrsOaZCaDjYpB8qWG+XHI= +go.opentelemetry.io/collector/internal/testutil v0.151.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= +go.opentelemetry.io/collector/pdata v1.57.0 h1:oDWBMjEIqyJO3GJEB+iwqxj47rxDK19OKzwaFEaE4sg= +go.opentelemetry.io/collector/pdata v1.57.0/go.mod h1:wZojinP6mNhLXudH8QXx/bjWzOsKMxi/FXwnk+12G/w= +go.opentelemetry.io/collector/pdata/pprofile v0.151.0 h1:hsU0+DpkvhJh3xL1Y8CX2vAPdLMoJLiw+C+rAMsaxZc= +go.opentelemetry.io/collector/pdata/pprofile v0.151.0/go.mod h1:5zfGTQqRuaKyh2SRaZi4SV4nSD8TzY1kYoOjniOD3uk= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.opentelemetry.io/proto/otlp/profiles/v1development v0.3.0 h1:ZQs05qo3Yh4KUHeVH6v89xErwmsvgA/cLX2/w5Ikp+k= go.opentelemetry.io/proto/otlp/profiles/v1development v0.3.0/go.mod h1:3iiRVKaCfVo0UI1ZaSMm5WbCBbINRqVlD9SUmvyBNrY= -go.opentelemetry.io/proto/slim/otlp v1.9.0 h1:fPVMv8tP3TrsqlkH1HWYUpbCY9cAIemx184VGkS6vlE= -go.opentelemetry.io/proto/slim/otlp v1.9.0/go.mod h1:xXdeJJ90Gqyll+orzUkY4bOd2HECo5JofeoLpymVqdI= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.2.0 h1:o13nadWDNkH/quoDomDUClnQBpdQQ2Qqv0lQBjIXjE8= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.2.0/go.mod h1:Gyb6Xe7FTi/6xBHwMmngGoHqL0w29Y4eW8TGFzpefGA= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.2.0 h1:EiUYvtwu6PMrMHVjcPfnsG3v+ajPkbUeH+IL93+QYyk= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.2.0/go.mod h1:mUUHKFiN2SST3AhJ8XhJxEoeVW12oqfXog0Bo8W3Ec4= +go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM= +go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE= -golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/arch v0.26.0 h1:jZ6dpec5haP/fUv1kLCbuJy6dnRrfX6iVK08lZBFpk4= +golang.org/x/arch v0.26.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -143,28 +143,28 @@ golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0= -golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260421165255-392afab6f40e h1:OXgN37M6hqjaAvb7CJK9vJ+7Z/6lvIm5bXho5poo/Wk= +golang.org/x/telemetry v0.0.0-20260421165255-392afab6f40e/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I= -golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s= +golang.org/x/vuln v1.3.0 h1:hZYzR8uRhYhDSX88d+40TWbKAVw7BIvRWm26rtEn8jw= +golang.org/x/vuln v1.3.0/go.mod h1:MIY2PaR1y52stzZM3uHBboUAdVJvSVMl5nP3OQrwQaE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= -google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= +google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/tools/stackdeltas/stackdeltas.go b/tools/stackdeltas/stackdeltas.go index 8ab23e278..cfedfc3a7 100644 --- a/tools/stackdeltas/stackdeltas.go +++ b/tools/stackdeltas/stackdeltas.go @@ -80,15 +80,21 @@ func getOpcode(baseReg uint8, param int32, deref bool) string { } func dumpDelta(delta sdtypes.StackDelta, merged bool) { - var cfa, fp string + var cfa, aux string info := &delta.Info if info.Flags&support.UnwindFlagCommand != 0 { cfa = getCommand(info.Param) } else { cfa = getOpcode(info.BaseReg, info.Param, info.Flags&support.UnwindFlagDerefCfa != 0) - fp = getOpcode(info.AuxBaseReg, info.AuxParam, false) + aux = getOpcode(info.AuxBaseReg, info.AuxParam, false) } comment := "" + if info.Flags&support.UnwindFlagRegisterRA != 0 { + comment += " ra-reg" + } + if info.Flags&support.UnwindFlagLeafOnly != 0 { + comment += " leaf-only" + } if delta.Hints&sdtypes.UnwindHintKeep != 0 { comment += " keep" } @@ -98,7 +104,7 @@ func dumpDelta(delta sdtypes.StackDelta, merged bool) { if merged { comment += " merged" } - fmt.Printf("%016x %-16s%-16s%s\n", delta.Address, cfa, fp, comment) + fmt.Printf("%016x %-16s%-16s%s\n", delta.Address, cfa, aux, comment) } func canMerge(delta, nextDelta sdtypes.StackDelta) bool { diff --git a/tracer/events.go b/tracer/events.go index bf79f3fe4..d2e365daa 100644 --- a/tracer/events.go +++ b/tracer/events.go @@ -35,6 +35,10 @@ const ( // events are produced by the kernel between two polling intervals, the queue from bpf // to userspace will fill up and the kernel will start dropping events. maxEvents = 4096 + + // eventReaderDeadline is the timeout for perf event reads. It allows the + // reader goroutine to periodically check for context cancellation. + eventReaderDeadline = 100 * time.Millisecond ) // StartPIDEventProcessor spawns a goroutine to process PID events. @@ -78,9 +82,6 @@ func (t *Tracer) triggerReportEvent(data []byte) { switch event.Type { case support.EventTypeGenericPID: t.handleGenericPID() - case support.EventTypeReloadKallsyms: - t.kernelSymbolizer.Reload() - t.enableEvent(support.EventTypeReloadKallsyms) } } @@ -100,28 +101,36 @@ func startPerfEventMonitor(ctx context.Context, perfEventMap *ebpf.Map, return nil, fmt.Errorf("Failed to setup perf reporting via %s: %v", perfEventMap, err) } + // Set a deadline so ReadInto times out periodically and we can check context + eventReader.SetDeadline(time.Now().Add(eventReaderDeadline)) + var lostEventsCount, readErrorCount, noDataCount atomic.Uint64 go func() { + defer eventReader.Close() var data perf.Record for { select { case <-ctx.Done(): return default: - if err := eventReader.ReadInto(&data); err != nil { + } + // Set a deadline so ReadInto times out and we can check context + eventReader.SetDeadline(time.Now().Add(eventReaderDeadline)) + if err := eventReader.ReadInto(&data); err != nil { + if !errors.Is(err, os.ErrDeadlineExceeded) { readErrorCount.Add(1) - continue - } - if data.LostSamples != 0 { - lostEventsCount.Add(data.LostSamples) - continue } - if len(data.RawSample) == 0 { - noDataCount.Add(1) - continue - } - triggerFunc(data.RawSample) + continue + } + if data.LostSamples != 0 { + lostEventsCount.Add(data.LostSamples) + continue + } + if len(data.RawSample) == 0 { + noDataCount.Add(1) + continue } + triggerFunc(data.RawSample) } }() @@ -156,6 +165,7 @@ func (t *Tracer) startTraceEventMonitor(ctx context.Context, var lostEventsCount, readErrorCount, noDataCount atomic.Uint64 go func() { + defer eventReader.Close() var data perf.Record var oldKTime, minKTime int64 var eventCount int diff --git a/tracer/helper.go b/tracer/helper.go index 75bfef12a..ca75aec37 100644 --- a/tracer/helper.go +++ b/tracer/helper.go @@ -50,7 +50,7 @@ func hasProbeReadBug(major, minor, patch uint32) bool { } // getOnlineCPUIDs reads online CPUs from /sys/devices/system/cpu/online and reports -// the core IDs as a list of integers. +// the core IDs as a list of integers. You should probably use `onlineCPUsOnce` instead. func getOnlineCPUIDs() ([]int, error) { cpuPath := "/sys/devices/system/cpu/online" buf, err := os.ReadFile(cpuPath) diff --git a/tracer/systemconfig.go b/tracer/systemconfig.go index 51781dd6a..43646123e 100644 --- a/tracer/systemconfig.go +++ b/tracer/systemconfig.go @@ -10,6 +10,7 @@ import ( "os" "runtime" "strings" + "syscall" "unsafe" "go.opentelemetry.io/ebpf-profiler/kallsyms" @@ -32,6 +33,11 @@ type sysConfigVars struct { stack_ptregs_offset uint32 } +var ( + errSystemAnalysisNotHandled = errors.New("system analysis request was not handled") + errSystemAnalysisFailed = errors.New("system analysis helper failed") +) + // memberByName resolves btf Member from a Struct with given name func memberByName(t *btf.Struct, field string) (*btf.Member, error) { for i, m := range t.Members { @@ -163,10 +169,29 @@ func executeSystemAnalysisBpfCode(progSpec *cebpf.ProgramSpec, maps map[string]* if err != nil { return nil, 0, fmt.Errorf("failed to get analysis data: %v", err) } + if err = validateSystemAnalysisResult(data, address); err != nil { + return nil, 0, err + } return data.Code[:], data.Address, nil } +func validateSystemAnalysisResult(data support.SystemAnalysis, address libpf.SymbolValue) error { + if data.Pid != 0 { + return fmt.Errorf("%w for pid %d at 0x%x", errSystemAnalysisNotHandled, data.Pid, address) + } + + if data.Err != 0 { + if data.Err < 0 { + return fmt.Errorf("%w at 0x%x: %w (helper err=%d)", errSystemAnalysisFailed, address, syscall.Errno(-data.Err), data.Err) + } + + return fmt.Errorf("%w at 0x%x: helper err=%d", errSystemAnalysisFailed, address, data.Err) + } + + return nil +} + // loadKernelCode will request the ebpf code to read the first X bytes from given address. func loadKernelCode(coll *cebpf.CollectionSpec, maps map[string]*cebpf.Map, address libpf.SymbolValue, @@ -242,6 +267,9 @@ func prepareAnalysis(orig *cebpf.CollectionSpec) (*cebpf.CollectionSpec, map[str } new.Maps["system_analysis"] = orig.Maps["system_analysis"].Copy() new.Maps[".rodata.var"] = orig.Maps[".rodata.var"].Copy() + if rodata, ok := orig.Maps[".rodata"]; ok { + new.Maps[".rodata"] = rodata.Copy() + } new.Programs["read_kernel_memory"] = orig.Programs["read_kernel_memory"].Copy() new.Programs["read_task_struct"] = orig.Programs["read_task_struct"].Copy() diff --git a/tracer/systemconfig_test.go b/tracer/systemconfig_test.go new file mode 100644 index 000000000..b11a35635 --- /dev/null +++ b/tracer/systemconfig_test.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package tracer + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/ebpf-profiler/libpf" + "go.opentelemetry.io/ebpf-profiler/support" +) + +func TestValidateSystemAnalysisResult(t *testing.T) { + address := libpf.SymbolValue(0x1234) + + t.Run("not handled", func(t *testing.T) { + err := validateSystemAnalysisResult(support.SystemAnalysis{Pid: 77}, address) + require.Error(t, err) + require.ErrorIs(t, err, errSystemAnalysisNotHandled) + require.ErrorContains(t, err, "pid 77") + }) + + t.Run("helper failure", func(t *testing.T) { + err := validateSystemAnalysisResult(support.SystemAnalysis{Err: -14}, address) + require.Error(t, err) + require.True(t, errors.Is(err, errSystemAnalysisFailed)) + require.ErrorContains(t, err, "helper err=-14") + }) + + t.Run("success", func(t *testing.T) { + err := validateSystemAnalysisResult(support.SystemAnalysis{}, address) + require.NoError(t, err) + }) +} diff --git a/tracer/tracer.go b/tracer/tracer.go index 5a626f71b..70e0dc11c 100644 --- a/tracer/tracer.go +++ b/tracer/tracer.go @@ -12,6 +12,8 @@ import ( "fmt" "math" "math/rand/v2" + "os" + "path" "slices" "strings" "sync" @@ -63,6 +65,12 @@ const ( schedProcessFreeV2 = "tracepoint__sched_process_free" ) +// Shared map name according to +// https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/main/devdocs/trace-profile-correlation.md +const ( + obiSpanTracesMap = "traces_ctx_v1" +) + // Intervals is a subset of config.IntervalsAndTimers. type Intervals interface { MonitorInterval() time.Duration @@ -71,6 +79,9 @@ type Intervals interface { ExecutableUnloadDelay() time.Duration } +// onlineCPUs once resolves and caches the list of online CPUs. +var onlineCPUsOnce = sync.OnceValues(getOnlineCPUIDs) + // Tracer provides an interface for loading and initializing the eBPF components as // well as for monitoring the output maps for new traces and count updates. type Tracer struct { @@ -96,10 +107,6 @@ type Tracer struct { // tracePool is cache of libpf.EbpfTrace to avoid GC pressure tracePool sync.Pool - // monitorPIDEventsMap iterates over the eBPF map pid_events, collects PIDs and - // writes them to the keys slice. The implementation is selected on creation. - monitorPIDEventsMapMethod func(keys *[]libpf.PIDTID) error - // triggerPIDProcessing is used as manual trigger channel to request immediate // processing of pending PIDs. This is requested on notifications from eBPF code // when process events take place (new, exit, unknown PC). @@ -114,8 +121,6 @@ type Tracer struct { // intervals provides access to globally configured timers and counters. intervals Intervals - hasBatchOperations bool - // samplesPerSecond holds the configured number of samples per second. samplesPerSecond int @@ -183,6 +188,10 @@ type Config struct { // LoadProbe indicates whether the generic eBPF program should be loaded // without being attached to something. LoadProbe bool + // BPFFSRoot is the root path to BPF filesystem for pinned maps and programs. + BPFFSRoot string + // OBIProcessCtx enable the use of a known shared eBPF map with OBI. + OBIProcessCtx bool } // hookPoint specifies the group and name of the hooked point in the kernel. @@ -252,9 +261,6 @@ func NewTracer(ctx context.Context, cfg *Config) (*Tracer, error) { return nil, fmt.Errorf("failed to load eBPF maps: %v", err) } - hasBatchOperations := ebpfHandler.SupportsGenericBatchOperations() - hasBatchLookupAndDelete := ebpfHandler.SupportsGenericBatchLookupAndDelete() - processManager, err := pm.New(ctx, cfg.IncludeTracers, cfg.Intervals.MonitorInterval(), cfg.Intervals.ExecutableUnloadDelay(), ebpfHandler, cfg.TraceReporter, cfg.ExecutableReporter, elfunwindinfo.NewStackDeltaProvider(), @@ -275,7 +281,6 @@ func NewTracer(ctx context.Context, cfg *Config) (*Tracer, error) { ebpfProgs: ebpfProgs, hooks: make(map[hookPoint]link.Link), intervals: cfg.Intervals, - hasBatchOperations: hasBatchOperations, perfEntrypoints: xsync.NewRWMutex(perfEventList), samplesPerSecond: cfg.SamplesPerSecond, probabilisticInterval: cfg.ProbabilisticInterval, @@ -283,13 +288,6 @@ func NewTracer(ctx context.Context, cfg *Config) (*Tracer, error) { done: make(chan libpf.Void), } - // Use an optimized version if available - if hasBatchLookupAndDelete { - tracer.monitorPIDEventsMapMethod = (*tracer).monitorPIDEventsMapBatch - } else { - tracer.monitorPIDEventsMapMethod = (*tracer).monitorPIDEventsMapSingle - } - return tracer, nil } @@ -310,6 +308,7 @@ func (t *Tracer) Close() { } t.processManager.Close() + t.kernelSymbolizer.Close() t.signalDone() } @@ -497,10 +496,6 @@ func initializeMapsAndPrograms(kmod *kallsyms.Module, cfg *Config) ( } } - if err = loadKallsymsTrigger(coll, ebpfProgs, cfg.BPFVerifierLogLevel); err != nil { - return nil, nil, nil, fmt.Errorf("failed to load kallsym eBPF program: %v", err) - } - if err = removeTemporaryMaps(ebpfMaps); err != nil { return nil, nil, nil, fmt.Errorf("failed to remove temporary maps: %v", err) } @@ -637,6 +632,24 @@ func loadAllMaps(coll *cebpf.CollectionSpec, cfg *Config, // Off CPU Profiling is disabled. So do not load this map. continue } + if mapName == obiSpanTracesMap { + if cfg.BPFFSRoot == "" || !cfg.OBIProcessCtx { + // As BPF FS is not set or process context sharing with OBI is not + // enabled, the map can not be shared with other OTel components. + // To reduce the memory footprint in this case reduce the size of the map. + mapSpec.MaxEntries = 1 + } else { + // Try to load it from a known path: + mPath := path.Join(cfg.BPFFSRoot, "otel", mapName) + ebpfMap, err := cebpf.LoadPinnedMap(mPath, &cebpf.LoadPinOptions{}) + if err == nil { + log.Infof("Using shared map for OBI span/trace ID communication") + ebpfMaps[mapName] = ebpfMap + continue + } + // The shared map does not yet exist or BPF FS is not set - so continue as usual + } + } if !types.IsMapEnabled(mapName, cfg.IncludeTracers) { log.Debugf("Skipping eBPF map %s: tracer not enabled", mapName) @@ -651,27 +664,32 @@ func loadAllMaps(coll *cebpf.CollectionSpec, cfg *Config, return fmt.Errorf("failed to load %s: %v", mapName, err) } ebpfMaps[mapName] = ebpfMap - } - - return nil -} - -// loadKallsymsTrigger loads the eBPF program that triggers kallsym updates. -func loadKallsymsTrigger(coll *cebpf.CollectionSpec, - ebpfProgs map[string]*cebpf.Program, bpfVerifierLogLevel uint32) error { - programOptions := cebpf.ProgramOptions{ - LogLevel: cebpf.LogLevel(bpfVerifierLogLevel), - } - kallsymsTriggerProg := "kprobe__kallsyms" - progSpec, ok := coll.Programs[kallsymsTriggerProg] - if !ok { - return fmt.Errorf("program %s does not exist", kallsymsTriggerProg) - } + if mapName == obiSpanTracesMap { + if cfg.BPFFSRoot == "" || !cfg.OBIProcessCtx { + // In environments, where BPF FS is not available, + // we just load the map to not break eBPF programs. + log.Infof("Skip pinning eBPF map to share OTel span/trace IDs") + continue + } - if err := loadProgram(ebpfProgs, nil, 0, progSpec, - programOptions, true); err != nil { - return err + // Pin the loaded map to a known path, so that other + // OTel components can also use it. + otelBPFFS := path.Join(cfg.BPFFSRoot, "otel") + if err := os.MkdirAll(otelBPFFS, 0o1700); err != nil { + // This is a non-fatal error for the functionality + // of the profiler. So just log it. + log.Warnf("Failed to create '%s'. OTel span/trace IDs can not be shared: %v", + otelBPFFS, err) + continue + } + if err := ebpfMap.Pin(path.Join(otelBPFFS, mapName)); err != nil { + // This is a non-fatal error for the functionality + // of the profiler. So just log it. + log.Warnf("Failed to pin '%s'. OTel span/trace IDs can not be shared: %v", + mapName, err) + } + } } return nil @@ -865,7 +883,11 @@ func (t *Tracer) symbolizeKernelFrames(addrs []uint64, oldFrames libpf.Frames) l Type: libpf.KernelFrame, AddressOrLineno: libpf.AddressOrLineno(address - 1), } - if kmod, err := t.kernelSymbolizer.GetModuleByAddress(address); err == nil { + if funcName, offset, ok := t.kernelSymbolizer.LookupBPFSymbol(address); ok { + // BPF program: use address relative to symbol start for deduplication. + frame.AddressOrLineno = libpf.AddressOrLineno(offset) + frame.FunctionName = libpf.Intern(funcName) + } else if kmod, err := t.kernelSymbolizer.GetModuleByAddress(address); err == nil { frame.Mapping = kmod.Mapping() frame.AddressOrLineno -= libpf.AddressOrLineno(kmod.Start()) if funcName, _, err := kmod.LookupSymbolByAddress(address); err == nil { @@ -887,72 +909,9 @@ func (t *Tracer) enableEvent(eventType int) { _ = inhibitEventsMap.Delete(unsafe.Pointer(&et)) } -// monitorPIDEventsMapSingle iterates over the eBPF map pid_events, collects PIDs -// and writes them to the keys slice. -func (t *Tracer) monitorPIDEventsMapSingle(keys *[]libpf.PIDTID) error { - eventsMap := t.ebpfMaps["pid_events"] - var key, nextKey uint64 - var value bool - keyFound := true - deleteBatch := make(libpf.Set[uint64]) - - // Key 0 retrieves the very first element in the hash map as - // it is guaranteed not to exist in pid_events. - key = 0 - if err := eventsMap.NextKey(unsafe.Pointer(&key), unsafe.Pointer(&nextKey)); err != nil { - if errors.Is(err, cebpf.ErrKeyNotExist) { - return nil - } - return fmt.Errorf("Failed to read from pid_events map: %v", err) - } - - for keyFound { - key = nextKey - - if err := eventsMap.Lookup(unsafe.Pointer(&key), unsafe.Pointer(&value)); err != nil { - return fmt.Errorf("Failed to lookup '%v' in pid_events: %v", key, err) - } - - // Lookup the next map entry before deleting the current one. - if err := eventsMap.NextKey(unsafe.Pointer(&key), unsafe.Pointer(&nextKey)); err != nil { - if !errors.Is(err, cebpf.ErrKeyNotExist) { - return fmt.Errorf("Failed to read from pid_events map: %v", err) - } - keyFound = false - } - - if !t.hasBatchOperations { - // Now that we have the next key, we can delete the current one. - if err := eventsMap.Delete(unsafe.Pointer(&key)); err != nil { - return fmt.Errorf("Failed to delete '%v' from pid_events: %v", key, err) - } - } else { - // Store to-be-deleted keys in a map so we can delete them all with a single - // bpf syscall. - deleteBatch[key] = libpf.Void{} - } - - // If we process keys inline with iteration (e.g. by sending them to t.pidEvents at this - // exact point), we may block sending to the channel, delay the iteration and may introduce - // race conditions (related to deletion). For that reason, keys are first collected and, - // after the iteration has finished, sent to the channel. - *keys = append(*keys, libpf.PIDTID(key)) - } - - keysToDelete := len(deleteBatch) - if keysToDelete != 0 { - keys := libpf.MapKeysToSlice(deleteBatch) - if _, err := eventsMap.BatchDelete(keys, nil); err != nil { - return fmt.Errorf("Failed to batch delete %d entries from pid_events map: %v", - keysToDelete, err) - } - } - return nil -} - -// monitorPIDEventsMapBatch iterates over the eBPF map pid_events in batches, +// monitorPIDEventsMap iterates over the eBPF map pid_events in batches, // collects PIDs and writes them to the keys slice. -func (t *Tracer) monitorPIDEventsMapBatch(keys *[]libpf.PIDTID) error { +func (t *Tracer) monitorPIDEventsMap(keys *[]libpf.PIDTID) error { eventsMap := t.ebpfMaps["pid_events"] removed := make([]uint64, 128) @@ -1073,7 +1032,7 @@ func (t *Tracer) loadBpfTrace(raw []byte, cpu int) (*libpf.EbpfTrace, error) { PID: pid, TID: libpf.PID(ptr.Tid), Origin: libpf.Origin(ptr.Origin), - OffTime: int64(ptr.Offtime), + Value: int64(ptr.Value), KTime: int64(ptr.Ktime), CPU: cpu, EnvVars: procMeta.EnvVariables, @@ -1116,7 +1075,12 @@ func (t *Tracer) loadBpfTrace(raw []byte, cpu int) (*libpf.EbpfTrace, error) { // StartMapMonitors starts goroutines for collecting metrics and monitoring eBPF // maps for tracepoints, new traces, trace count updates and unknown PCs. func (t *Tracer) StartMapMonitors(ctx context.Context, traceOutChan chan<- *libpf.EbpfTrace) error { - if err := t.kernelSymbolizer.StartMonitor(ctx); err != nil { + onlineCPUs, err := onlineCPUsOnce() + if err != nil { + return fmt.Errorf("failed to get online cpus: %w", err) + } + + if err := t.kernelSymbolizer.StartMonitor(ctx, onlineCPUs); err != nil { log.Warnf("Failed to start kallsyms monitor: %v", err) } eventMetricCollector, err := t.startEventMonitor(ctx) @@ -1132,7 +1096,7 @@ func (t *Tracer) StartMapMonitors(ctx context.Context, traceOutChan chan<- *libp periodiccaller.StartWithManualTrigger(ctx, t.intervals.MonitorInterval(), t.triggerPIDProcessing, func(_ bool) bool { t.enableEvent(support.EventTypeGenericPID) - err := t.monitorPIDEventsMapMethod(&pidEvents) + err := t.monitorPIDEventsMap(&pidEvents) if err != nil { log.Errorf("Failed to monitor PID events: %v", err) t.signalDone() @@ -1166,33 +1130,6 @@ func (t *Tracer) StartMapMonitors(ctx context.Context, traceOutChan chan<- *libp return nil } -func (t *Tracer) attachToKallsymsUpdates() error { - prog, ok := t.ebpfProgs["kprobe__kallsyms"] - if !ok { - return fmt.Errorf("kprobe__kallsyms is not available") - } - - kallsymsAttachPoint := "bpf_ksym_add" - kmod, err := t.kernelSymbolizer.GetModuleByName(kallsyms.Kernel) - if err != nil { - return err - } - - if _, err := kmod.LookupSymbol(kallsymsAttachPoint); err != nil { - log.Infof("Monitoring kallsyms is supported only for Linux kernel 5.8 or greater: %s: %v", - kallsymsAttachPoint, err) - return nil - } - - hook, err := link.Kprobe(kallsymsAttachPoint, prog, nil) - if err != nil { - return fmt.Errorf("failed opening kprobe for kallsyms trigger: %s", err) - } - t.hooks[hookPoint{group: "kprobe", name: kallsymsAttachPoint}] = hook - - return nil -} - // terminatePerfEvents disables perf events and closes their file descriptor. func terminatePerfEvents(events []*perf.Event) { for _, event := range events { @@ -1220,14 +1157,14 @@ func (t *Tracer) AttachTracer() error { return fmt.Errorf("failed to configure software perf event: %v", err) } - onlineCPUIDs, err := getOnlineCPUIDs() + onlineCPUs, err := onlineCPUsOnce() if err != nil { - return fmt.Errorf("failed to get online CPUs: %v", err) + return fmt.Errorf("failed to get online cpus: %w", err) } events := t.perfEntrypoints.WLock() defer t.perfEntrypoints.WUnlock(&events) - for _, id := range onlineCPUIDs { + for _, id := range onlineCPUs { perfEvent, err := perf.Open(perfAttribute, perf.AllThreads, id, nil) if err != nil { terminatePerfEvents(*events) @@ -1240,11 +1177,6 @@ func (t *Tracer) AttachTracer() error { *events = append(*events, perfEvent) } - if err = t.attachToKallsymsUpdates(); err != nil { - terminatePerfEvents(*events) - return err - } - return nil } diff --git a/tracer/types/parse.go b/tracer/types/parse.go index 78621968a..d06cf7bd1 100644 --- a/tracer/types/parse.go +++ b/tracer/types/parse.go @@ -22,6 +22,7 @@ const ( RubyTracer V8Tracer DotnetTracer + LuaJITTracer GoTracer Labels BEAMTracer @@ -38,6 +39,7 @@ var tracerTypeToName = map[tracerType]string{ RubyTracer: "ruby", V8Tracer: "v8", DotnetTracer: "dotnet", + LuaJITTracer: "luajit", GoTracer: "go", Labels: "labels", BEAMTracer: "beam",