diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 22ab798173..eaad6b280f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -13,6 +13,11 @@ jobs: name: Run OpenTelemetry-cpp benchmarks runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -47,6 +52,11 @@ jobs: name: Store benchmark result runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # main March 2025 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a077bc5ca5..6c27783a0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ main ] +permissions: + contents: read + jobs: # Commented 2024-11-06, lack of workers in github causes CI failures @@ -37,6 +40,11 @@ jobs: env: CXX_STANDARD: '17' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -53,6 +61,11 @@ jobs: name: CMake gcc 14 (maintainer mode, sync) runs-on: ubuntu-24.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -90,6 +103,11 @@ jobs: name: CMake gcc 14 (maintainer mode, async) runs-on: ubuntu-24.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -127,6 +145,11 @@ jobs: name: CMake clang 18 (maintainer mode, sync) runs-on: ubuntu-24.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -164,6 +187,11 @@ jobs: name: CMake clang 18 (maintainer mode, async) runs-on: ubuntu-24.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -201,6 +229,11 @@ jobs: name: CMake clang 18 (maintainer mode, abiv2) runs-on: ubuntu-24.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -238,6 +271,11 @@ jobs: name: CMake msvc (maintainer mode) runs-on: windows-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -252,6 +290,11 @@ jobs: name: CMake msvc (maintainer mode) with C++20 runs-on: windows-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -268,6 +311,11 @@ jobs: name: CMake msvc (maintainer mode, abiv2) runs-on: windows-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -284,6 +332,11 @@ jobs: name: CMake test (without otlp-exporter and with async export) runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -306,6 +359,11 @@ jobs: name: CMake test (with opentracing-shim) runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -321,6 +379,11 @@ jobs: name: CMake C++14 test(GCC) runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -338,6 +401,11 @@ jobs: name: CMake C++17 test(GCC) runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -355,6 +423,11 @@ jobs: name: CMake C++20 test(GCC) runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -376,6 +449,11 @@ jobs: name: CMake C++20 test(Clang with libc++) runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -407,6 +485,11 @@ jobs: name: CMake C++23 test(GCC) runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -428,6 +511,11 @@ jobs: name: CMake C++23 test(Clang with libc++) runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -459,6 +547,11 @@ jobs: name: CMake test (with otlp-exporter) runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -485,6 +578,11 @@ jobs: name: CMake test (with modern protobuf,grpc and abseil) runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -510,6 +608,11 @@ jobs: name: CMake do not install test (with otlp-exporter) runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -527,6 +630,11 @@ jobs: name: CMake test (build shared libraries with otlp-exporter and static gRPC) runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -544,6 +652,11 @@ jobs: name: Plugin -> CMake runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -565,6 +678,11 @@ jobs: name: Bazel runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -587,6 +705,11 @@ jobs: name: Bazel without bzlmod runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -609,6 +732,11 @@ jobs: name: Bazel with async export runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -631,6 +759,11 @@ jobs: name: Bazel valgrind runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -653,6 +786,11 @@ jobs: name: Bazel noexcept runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -675,6 +813,11 @@ jobs: name: Bazel nortti runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -697,6 +840,11 @@ jobs: name: Bazel asan config runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -719,6 +867,11 @@ jobs: name: Bazel tsan config runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -741,6 +894,11 @@ jobs: name: Bazel on MacOS runs-on: macos-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -758,6 +916,11 @@ jobs: name: Benchmark runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -786,6 +949,11 @@ jobs: name: Format runs-on: ubuntu-24.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: setup run: sudo apt remove needrestart && sudo ./ci/install_format_tools.sh #refer: https://github.com/actions/runner-images/issues/9937 @@ -796,6 +964,11 @@ jobs: name: Copyright runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: check copyright run: ./tools/check_copyright.sh @@ -804,6 +977,11 @@ jobs: name: CMake -> exporter proto runs-on: windows-2019 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -821,6 +999,11 @@ jobs: name: CMake -> exporter proto (Build as DLL) runs-on: windows-2019 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -840,6 +1023,11 @@ jobs: name: CMake (With async export) -> exporter proto runs-on: windows-2019 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -857,6 +1045,11 @@ jobs: name: Bazel Windows runs-on: windows-2019 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -870,6 +1063,11 @@ jobs: name: Plugin -> CMake Windows runs-on: windows-2019 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -884,6 +1082,11 @@ jobs: name: Code coverage runs-on: ubuntu-22.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -909,6 +1112,11 @@ jobs: runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - name: check out code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -921,6 +1129,11 @@ jobs: shellcheck: runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - name: check out code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: install shellcheck @@ -931,6 +1144,11 @@ jobs: misspell: runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - name: check out code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: install misspell @@ -944,6 +1162,11 @@ jobs: name: DocFX check runs-on: windows-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: install docfx run: choco install docfx -y --version=2.58.5 @@ -955,6 +1178,11 @@ jobs: name: W3C Distributed Tracing Validation V1 runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - name: Checkout open-telemetry/opentelemetry-cpp uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 8a2c3c1b9b..aec027f64b 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -6,10 +6,18 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: clang-tidy: runs-on: ubuntu-24.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive diff --git a/.github/workflows/cmake_install.yml b/.github/workflows/cmake_install.yml index 323eb6cebb..c62d7def82 100644 --- a/.github/workflows/cmake_install.yml +++ b/.github/workflows/cmake_install.yml @@ -7,6 +7,9 @@ on: pull_request: branches: [ main ] +permissions: + contents: read + jobs: windows_2022_vcpkg_submodule: name: Windows 2022 vcpkg submodule versions cxx17 (static libs - dll) @@ -17,6 +20,11 @@ jobs: # cxx17 is the default for windows-2022 CXX_STANDARD: '17' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -38,6 +46,11 @@ jobs: # cxx14 is the default for windows-2019 CXX_STANDARD: '14' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -59,6 +72,11 @@ jobs: CXX_STANDARD: '17' BUILD_TYPE: 'Debug' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -98,6 +116,11 @@ jobs: GRPC_VERSION: 'v1.71.0' BUILD_TYPE: 'Debug' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -132,6 +155,11 @@ jobs: GRPC_VERSION: 'v1.55.0' BUILD_TYPE: 'Debug' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -171,6 +199,11 @@ jobs: GRPC_VERSION: 'v1.49.2' BUILD_TYPE: 'Debug' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -204,6 +237,11 @@ jobs: CMAKE_TOOLCHAIN_FILE: /home/runner/conan/build/Debug/generators/conan_toolchain.cmake BUILD_TYPE: 'Debug' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -242,6 +280,11 @@ jobs: CMAKE_TOOLCHAIN_FILE: /home/runner/conan/build/Debug/generators/conan_toolchain.cmake BUILD_TYPE: 'Debug' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -273,6 +316,11 @@ jobs: CMAKE_TOOLCHAIN_FILE: '/Users/runner/conan/build/Debug/generators/conan_toolchain.cmake' BUILD_TYPE: 'Debug' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' @@ -298,6 +346,11 @@ jobs: CXX_STANDARD: '17' BUILD_TYPE: 'Debug' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 684e925c37..f7e1996d11 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -7,10 +7,22 @@ on: # The branches below must be a subset of the branches above branches: [main] +permissions: + contents: read + jobs: CodeQL-Build: + permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/autobuild to send a status report runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml index a1239c75a1..53057354bc 100644 --- a/.github/workflows/cppcheck.yml +++ b/.github/workflows/cppcheck.yml @@ -7,10 +7,18 @@ on: pull_request: branches: [ main ] +permissions: + contents: read + jobs: cppcheck: runs-on: ubuntu-24.04 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' diff --git a/.github/workflows/dependencies_image.yml b/.github/workflows/dependencies_image.yml index ab37ce80ef..16f04b7d4c 100644 --- a/.github/workflows/dependencies_image.yml +++ b/.github/workflows/dependencies_image.yml @@ -3,12 +3,20 @@ on: schedule: - cron: "0 3 * * 6" +permissions: + contents: read + jobs: docker_image: name: Docker Image runs-on: ubuntu-latest timeout-minutes: 300 steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index ff73a43424..f45c8a9451 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -12,6 +12,11 @@ jobs: fossa: runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: fossas/fossa-action@c0a7d013f84c8ee5e910593186598625513cc1e4 # v1.6.0 diff --git a/.github/workflows/iwyu.yml b/.github/workflows/iwyu.yml index 5fcedf5105..42881ec6b6 100644 --- a/.github/workflows/iwyu.yml +++ b/.github/workflows/iwyu.yml @@ -14,6 +14,11 @@ jobs: iwyu: runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: 'recursive' diff --git a/.github/workflows/project_management_comment.yml b/.github/workflows/project_management_comment.yml index bddf31549a..6e64cf0aac 100644 --- a/.github/workflows/project_management_comment.yml +++ b/.github/workflows/project_management_comment.yml @@ -4,6 +4,9 @@ on: issues: types: - labeled +permissions: + contents: read + jobs: add-comment: if: github.event.label.name == 'help wanted' @@ -11,6 +14,11 @@ jobs: permissions: issues: write steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - name: Add comment uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 with: diff --git a/.github/workflows/project_management_issue_open.yml b/.github/workflows/project_management_issue_open.yml index c9291b1dcd..14750d1a67 100644 --- a/.github/workflows/project_management_issue_open.yml +++ b/.github/workflows/project_management_issue_open.yml @@ -13,6 +13,11 @@ jobs: permissions: issues: write steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: script: | diff --git a/.vscode/launch.json b/.vscode/launch.json index 3532ca4d2a..c7ad6761dd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,24 +1,33 @@ { "version": "0.2.0", "configurations": [ - { - "name": "Debug on Windows", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/build/", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": false - }, - { - "name": "Debug on Linux", - "type": "gdb", - "request": "launch", - "target": "${workspaceFolder}/bazel-bin/", - "cwd": "${workspaceRoot}", - "valuesFormatting": "parseText" - } + { + "name": "(ctest) Launch", + "type": "cppdbg", + "cwd": "${cmake.testWorkingDirectory}", + "request": "launch", + "program": "${cmake.testProgram}", + "args": [ "${cmake.testArgs}" ], + // other options... + }, + { + "name": "Debug on Windows", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/build/", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false + }, + { + "name": "Debug on Linux", + "type": "gdb", + "request": "launch", + "target": "${workspaceFolder}/bazel-bin/", + "cwd": "${workspaceRoot}", + "valuesFormatting": "parseText" + } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c42855fee..f01050441d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,15 @@ Increment the: ## [Unreleased] +* [SDK] Base2 exponential histogram aggregation + [#3175](https://github.com/open-telemetry/opentelemetry-cpp/pull/3346) + + New Features: + + Add base2 exponential histogram aggregation. Includes a new aggregation type, + ostream exporter, and otlp/grpc exporter. Updated histogram aggregation and + benchmark tests. + * [API] Remove `WITH_ABSEIL` and `HAVE_ABSEIL` [#3318](https://github.com/open-telemetry/opentelemetry-cpp/pull/3318) diff --git a/examples/common/metrics_foo_library/foo_library.cc b/examples/common/metrics_foo_library/foo_library.cc index 81cb840718..5f1fb86776 100644 --- a/examples/common/metrics_foo_library/foo_library.cc +++ b/examples/common/metrics_foo_library/foo_library.cc @@ -100,7 +100,24 @@ void foo_library::histogram_example(const std::string &name) std::string histogram_name = name + "_histogram"; auto provider = metrics_api::Provider::GetMeterProvider(); opentelemetry::nostd::shared_ptr meter = provider->GetMeter(name, "1.2.0"); - auto histogram_counter = meter->CreateDoubleHistogram(histogram_name, "des", "unit"); + auto histogram_counter = meter->CreateDoubleHistogram(histogram_name, "des", "histogram-unit"); + auto context = opentelemetry::context::Context{}; + for (uint32_t i = 0; i < 20; ++i) + { + double val = (rand() % 700) + 1.1; + std::map labels = get_random_attr(); + auto labelkv = opentelemetry::common::KeyValueIterableView{labels}; + histogram_counter->Record(val, labelkv, context); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } +} + +void foo_library::histogram_exp_example(const std::string &name) +{ + std::string histogram_name = name + "_exponential_histogram"; + auto provider = metrics_api::Provider::GetMeterProvider(); + auto meter = provider->GetMeter(name, "1.2.0"); + auto histogram_counter = meter->CreateDoubleHistogram(histogram_name, "des", "histogram-unit"); auto context = opentelemetry::context::Context{}; for (uint32_t i = 0; i < 20; ++i) { diff --git a/examples/common/metrics_foo_library/foo_library.h b/examples/common/metrics_foo_library/foo_library.h index 65997398a5..d157a78f28 100644 --- a/examples/common/metrics_foo_library/foo_library.h +++ b/examples/common/metrics_foo_library/foo_library.h @@ -10,6 +10,7 @@ class foo_library public: static void counter_example(const std::string &name); static void histogram_example(const std::string &name); + static void histogram_exp_example(const std::string &name); static void observable_counter_example(const std::string &name); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 static void gauge_example(const std::string &name); diff --git a/examples/metrics_simple/metrics_ostream.cc b/examples/metrics_simple/metrics_ostream.cc index 1d85ff1692..a7b6bcdd08 100644 --- a/examples/metrics_simple/metrics_ostream.cc +++ b/examples/metrics_simple/metrics_ostream.cc @@ -15,6 +15,8 @@ #include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h" #include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_options.h" #include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/metrics/meter_context.h" +#include "opentelemetry/sdk/metrics/meter_context_factory.h" #include "opentelemetry/sdk/metrics/meter_provider.h" #include "opentelemetry/sdk/metrics/meter_provider_factory.h" #include "opentelemetry/sdk/metrics/metric_reader.h" @@ -56,9 +58,9 @@ void InitMetrics(const std::string &name) auto reader = metrics_sdk::PeriodicExportingMetricReaderFactory::Create(std::move(exporter), options); - auto provider = opentelemetry::sdk::metrics::MeterProviderFactory::Create(); - - provider->AddMetricReader(std::move(reader)); + auto context = metrics_sdk::MeterContextFactory::Create(); + context->AddMetricReader(std::move(reader)); + auto provider = opentelemetry::sdk::metrics::MeterProviderFactory::Create(std::move(context)); // counter view std::string counter_name = name + "_counter"; @@ -112,6 +114,30 @@ void InitMetrics(const std::string &name) provider->AddView(std::move(histogram_instrument_selector), std::move(histogram_meter_selector), std::move(histogram_view)); + // hisogram view with base2 exponential aggregation + std::string histogram_base2_name = name + "_exponential_histogram"; + unit = "histogram-unit"; + auto histogram_base2_instrument_selector = metrics_sdk::InstrumentSelectorFactory::Create( + metrics_sdk::InstrumentType::kHistogram, histogram_base2_name, unit); + auto histogram_base2_meter_selector = + metrics_sdk::MeterSelectorFactory::Create(name, version, schema); + auto histogram_base2_aggregation_config = + std::unique_ptr( + new metrics_sdk::Base2ExponentialHistogramAggregationConfig); + histogram_base2_aggregation_config->max_scale_ = 3; + histogram_base2_aggregation_config->record_min_max_ = true; + histogram_base2_aggregation_config->max_buckets_ = 100; + + std::shared_ptr base2_aggregation_config( + std::move(histogram_base2_aggregation_config)); + + auto histogram_base2_view = metrics_sdk::ViewFactory::Create( + name, "description", unit, metrics_sdk::AggregationType::kBase2ExponentialHistogram, + base2_aggregation_config); + + provider->AddView(std::move(histogram_base2_instrument_selector), + std::move(histogram_base2_meter_selector), std::move(histogram_base2_view)); + std::shared_ptr api_provider(std::move(provider)); metrics_sdk::Provider::SetMeterProvider(api_provider); @@ -147,6 +173,10 @@ int main(int argc, char **argv) { foo_library::histogram_example(name); } + else if (example_type == "exponential_histogram") + { + foo_library::histogram_exp_example(name); + } #if OPENTELEMETRY_ABI_VERSION_NO >= 2 else if (example_type == "gauge") { @@ -170,6 +200,7 @@ int main(int argc, char **argv) std::thread counter_example{&foo_library::counter_example, name}; std::thread observable_counter_example{&foo_library::observable_counter_example, name}; std::thread histogram_example{&foo_library::histogram_example, name}; + std::thread histogram_exp_example{&foo_library::histogram_exp_example, name}; #if OPENTELEMETRY_ABI_VERSION_NO >= 2 std::thread gauge_example{&foo_library::gauge_example, name}; #endif @@ -181,6 +212,7 @@ int main(int argc, char **argv) counter_example.join(); observable_counter_example.join(); histogram_example.join(); + histogram_exp_example.join(); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 gauge_example.join(); #endif diff --git a/examples/otlp/grpc_metric_main.cc b/examples/otlp/grpc_metric_main.cc index 7b6550edba..761eabd4f1 100644 --- a/examples/otlp/grpc_metric_main.cc +++ b/examples/otlp/grpc_metric_main.cc @@ -1,6 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#include "grpcpp/grpcpp.h" +#include "opentelemetry/exporters/otlp/otlp_grpc_exporter.h" + #include "opentelemetry/exporters/otlp/otlp_grpc_metric_exporter_factory.h" #include "opentelemetry/metrics/provider.h" #include "opentelemetry/sdk/metrics/aggregation/default_aggregation.h" @@ -11,6 +14,9 @@ #include "opentelemetry/sdk/metrics/meter_provider.h" #include "opentelemetry/sdk/metrics/meter_provider_factory.h" #include "opentelemetry/sdk/metrics/provider.h" +#include "opentelemetry/sdk/metrics/view/instrument_selector_factory.h" +#include "opentelemetry/sdk/metrics/view/meter_selector_factory.h" +#include "opentelemetry/sdk/metrics/view/view_factory.h" #include #include @@ -31,7 +37,7 @@ namespace otlp_exporter::OtlpGrpcMetricExporterOptions exporter_options; -void InitMetrics() +void InitMetrics(std::string &name) { auto exporter = otlp_exporter::OtlpGrpcMetricExporterFactory::Create(exporter_options); @@ -49,10 +55,34 @@ void InitMetrics() auto context = metric_sdk::MeterContextFactory::Create(); context->AddMetricReader(std::move(reader)); - auto u_provider = metric_sdk::MeterProviderFactory::Create(std::move(context)); - std::shared_ptr provider(std::move(u_provider)); + auto provider = metric_sdk::MeterProviderFactory::Create(std::move(context)); + + // histogram view + std::string histogram_name = name + "_exponential_histogram"; + std::string unit = "histogram-unit"; + + auto histogram_instrument_selector = metric_sdk::InstrumentSelectorFactory::Create( + metric_sdk::InstrumentType::kHistogram, histogram_name, unit); + + auto histogram_meter_selector = metric_sdk::MeterSelectorFactory::Create(name, version, schema); + + auto histogram_aggregation_config = + std::unique_ptr( + new metric_sdk::Base2ExponentialHistogramAggregationConfig); + + std::shared_ptr aggregation_config( + std::move(histogram_aggregation_config)); + + auto histogram_view = metric_sdk::ViewFactory::Create( + name, "des", unit, metric_sdk::AggregationType::kBase2ExponentialHistogram, + aggregation_config); - metric_sdk::Provider::SetMeterProvider(provider); + provider->AddView(std::move(histogram_instrument_selector), std::move(histogram_meter_selector), + std::move(histogram_view)); + + std::shared_ptr api_provider(std::move(provider)); + + metric_sdk::Provider::SetMeterProvider(api_provider); } void CleanupMetrics() @@ -78,10 +108,17 @@ int main(int argc, char *argv[]) } } } + std::cout << "Using endpoint: " << exporter_options.endpoint << std::endl; + std::cout << "Using example type: " << example_type << std::endl; + std::cout << "Using cacert path: " << exporter_options.ssl_credentials_cacert_path << std::endl; + std::cout << "Using ssl credentials: " << exporter_options.use_ssl_credentials << std::endl; + // Removing this line will leave the default noop MetricProvider in place. - InitMetrics(); + std::string name{"otlp_grpc_metric_example"}; + InitMetrics(name); + if (example_type == "counter") { foo_library::counter_example(name); @@ -94,6 +131,10 @@ int main(int argc, char *argv[]) { foo_library::histogram_example(name); } + else if (example_type == "exponential_histogram") + { + foo_library::histogram_exp_example(name); + } #if OPENTELEMETRY_ABI_VERSION_NO >= 2 else if (example_type == "gauge") { @@ -105,6 +146,7 @@ int main(int argc, char *argv[]) std::thread counter_example{&foo_library::counter_example, name}; std::thread observable_counter_example{&foo_library::observable_counter_example, name}; std::thread histogram_example{&foo_library::histogram_example, name}; + std::thread histogram_exp_example{&foo_library::histogram_exp_example, name}; #if OPENTELEMETRY_ABI_VERSION_NO >= 2 std::thread gauge_example{&foo_library::gauge_example, name}; #endif @@ -112,6 +154,7 @@ int main(int argc, char *argv[]) counter_example.join(); observable_counter_example.join(); histogram_example.join(); + histogram_exp_example.join(); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 gauge_example.join(); #endif diff --git a/exporters/ostream/src/metric_exporter.cc b/exporters/ostream/src/metric_exporter.cc index 4bdd122492..39f5b017e7 100644 --- a/exporters/ostream/src/metric_exporter.cc +++ b/exporters/ostream/src/metric_exporter.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,7 @@ #include "opentelemetry/sdk/common/exporter_utils.h" #include "opentelemetry/sdk/common/global_log_handler.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/metrics/data/circular_buffer.h" #include "opentelemetry/sdk/metrics/data/metric_data.h" #include "opentelemetry/sdk/metrics/data/point_data.h" #include "opentelemetry/sdk/metrics/export/metric_producer.h" @@ -247,6 +249,43 @@ void OStreamMetricExporter::printPointData(const opentelemetry::sdk::metrics::Po sout_ << nostd::get(last_point_data.value_); } } + else if (nostd::holds_alternative(point_data)) + { + auto histogram_point_data = + nostd::get(point_data); + if (!histogram_point_data.positive_buckets_ && !histogram_point_data.negative_buckets_) + { + return; + } + sout_ << "\n type: Base2ExponentialHistogramPointData"; + sout_ << "\n count: " << histogram_point_data.count_; + sout_ << "\n sum: " << histogram_point_data.sum_; + sout_ << "\n zero_count: " << histogram_point_data.zero_count_; + if (histogram_point_data.record_min_max_) + { + sout_ << "\n min: " << histogram_point_data.min_; + sout_ << "\n max: " << histogram_point_data.max_; + } + sout_ << "\n scale: " << histogram_point_data.scale_; + sout_ << "\n positive buckets:"; + if (!histogram_point_data.positive_buckets_->Empty()) + { + for (auto i = histogram_point_data.positive_buckets_->StartIndex(); + i <= histogram_point_data.positive_buckets_->EndIndex(); ++i) + { + sout_ << "\n\t" << i << ": " << histogram_point_data.positive_buckets_->Get(i); + } + } + sout_ << "\n negative buckets:"; + if (!histogram_point_data.negative_buckets_->Empty()) + { + for (auto i = histogram_point_data.negative_buckets_->StartIndex(); + i <= histogram_point_data.negative_buckets_->EndIndex(); ++i) + { + sout_ << "\n\t" << i << ": " << histogram_point_data.negative_buckets_->Get(i); + } + } + } } void OStreamMetricExporter::printPointAttributes( diff --git a/exporters/ostream/test/ostream_metric_test.cc b/exporters/ostream/test/ostream_metric_test.cc index 6e7a88fa2f..de79c3c757 100644 --- a/exporters/ostream/test/ostream_metric_test.cc +++ b/exporters/ostream/test/ostream_metric_test.cc @@ -13,6 +13,7 @@ #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/sdk/common/exporter_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/metrics/data/circular_buffer.h" #include "opentelemetry/sdk/metrics/data/metric_data.h" #include "opentelemetry/sdk/metrics/data/point_data.h" #include "opentelemetry/sdk/metrics/export/metric_producer.h" @@ -179,6 +180,115 @@ TEST(OStreamMetricsExporter, ExportHistogramPointData) ASSERT_EQ(stdoutOutput.str(), expected_output); } +TEST(OStreamMetricsExporter, ExportBase2ExponentialHistogramPointData) +{ + auto exporter = + std::unique_ptr(new exportermetrics::OStreamMetricExporter); + + metric_sdk::Base2ExponentialHistogramPointData histogram_point_data1; + histogram_point_data1.count_ = 3; + histogram_point_data1.sum_ = 6.5; + histogram_point_data1.min_ = 0.0; + histogram_point_data1.max_ = 3.5; + histogram_point_data1.scale_ = 3; + histogram_point_data1.record_min_max_ = true; + histogram_point_data1.zero_count_ = 1; + histogram_point_data1.positive_buckets_ = + std::make_unique(10); + histogram_point_data1.negative_buckets_ = + std::make_unique(10); + histogram_point_data1.positive_buckets_->Increment(1, 1); + histogram_point_data1.negative_buckets_->Increment(-2, 1); + + metric_sdk::Base2ExponentialHistogramPointData histogram_point_data2; + histogram_point_data2.count_ = 4; + histogram_point_data2.sum_ = 6.2; + histogram_point_data2.min_ = -0.03; + histogram_point_data2.max_ = 3.5; + histogram_point_data2.scale_ = 3; + histogram_point_data2.record_min_max_ = false; + histogram_point_data2.zero_count_ = 2; + histogram_point_data2.positive_buckets_ = + std::make_unique(10); + histogram_point_data2.negative_buckets_ = + std::make_unique(10); + histogram_point_data2.positive_buckets_->Increment(3, 1); + histogram_point_data2.negative_buckets_->Increment(-2, 1); + histogram_point_data2.negative_buckets_->Increment(-4, 2); + + metric_sdk::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto scope = opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create( + "library_name", "1.2.0"); + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kCounter, + metric_sdk::InstrumentValueType::kDouble}, + metric_sdk::AggregationTemporality::kDelta, opentelemetry::common::SystemTimestamp{}, + opentelemetry::common::SystemTimestamp{}, + std::vector{ + {metric_sdk::PointAttributes{{"a1", "b1"}, {"a2", "b2"}}, histogram_point_data1}, + {metric_sdk::PointAttributes{{"a1", "b1"}}, histogram_point_data2}}}; + data.scope_metric_data_ = std::vector{ + {scope.get(), std::vector{metric_data}}}; + + std::stringstream stdoutOutput; + std::streambuf *sbuf = std::cout.rdbuf(); + std::cout.rdbuf(stdoutOutput.rdbuf()); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + std::cout.rdbuf(sbuf); + + std::string expected_output = + "{" + "\n scope name\t: library_name" + "\n schema url\t: " + "\n version\t: 1.2.0" + "\n start time\t: Thu Jan 1 00:00:00 1970" + "\n end time\t: Thu Jan 1 00:00:00 1970" + "\n instrument name\t: library_name" + "\n description\t: description" + "\n unit\t\t: unit" + "\n type: Base2ExponentialHistogramPointData" + "\n count: 3" + "\n sum: 6.5" + "\n zero_count: 1" + "\n min: 0" + "\n max: 3.5" + "\n scale: 3" + "\n positive buckets:" + "\n\t1: 1" + "\n negative buckets:" + "\n\t-2: 1" + "\n attributes\t\t: " + "\n\ta1: b1" + "\n\ta2: b2" + "\n type: Base2ExponentialHistogramPointData" + "\n count: 4" + "\n sum: 6.2" + "\n zero_count: 2" + "\n scale: 3" + "\n positive buckets:" + "\n\t3: 1" + "\n negative buckets:" + "\n\t-4: 2" + "\n\t-3: 0" + "\n\t-2: 1" + "\n attributes\t\t: " + "\n\ta1: b1" + "\n resources\t:" + "\n\tservice.name: unknown_service" + "\n\ttelemetry.sdk.language: cpp" + "\n\ttelemetry.sdk.name: opentelemetry" + "\n\ttelemetry.sdk.version: "; + expected_output += OPENTELEMETRY_SDK_VERSION; + expected_output += "\n}\n"; + ASSERT_EQ(stdoutOutput.str(), expected_output); +} + TEST(OStreamMetricsExporter, ExportLastValuePointData) { auto exporter = diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index cdaba37e4d..ba55ba72c7 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -417,6 +417,7 @@ cc_test( ], deps = [ ":otlp_recordable", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -433,6 +434,7 @@ cc_test( ], deps = [ ":otlp_recordable", + "//sdk/src/metrics", "@com_github_opentelemetry_proto//:logs_service_proto_cc", "@com_google_googletest//:gtest_main", ], @@ -451,6 +453,7 @@ cc_test( ":otlp_file_exporter", ":otlp_recordable", "//api", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -466,6 +469,7 @@ cc_test( deps = [ ":otlp_grpc_exporter", "//api", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -481,6 +485,7 @@ cc_test( deps = [ ":otlp_grpc_exporter", "//api", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -496,6 +501,7 @@ cc_test( deps = [ ":otlp_http_exporter", "//api", + "//sdk/src/metrics", "//test_common/src/http/client/nosend:http_client_nosend", "@com_google_googletest//:gtest_main", ], @@ -512,6 +518,7 @@ cc_test( deps = [ ":otlp_http_exporter", "//api", + "//sdk/src/metrics", "//test_common/src/http/client/nosend:http_client_nosend", "@com_google_googletest//:gtest_main", ], @@ -528,6 +535,7 @@ cc_test( deps = [ ":otlp_file_exporter", "//api", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -543,6 +551,7 @@ cc_test( deps = [ ":otlp_file_exporter", "//api", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -558,6 +567,7 @@ cc_test( deps = [ ":otlp_http_log_record_exporter", "//api", + "//sdk/src/metrics", "//test_common/src/http/client/nosend:http_client_nosend", "@com_google_googletest//:gtest_main", ], @@ -574,6 +584,7 @@ cc_test( deps = [ ":otlp_http_log_record_exporter", "//api", + "//sdk/src/metrics", "//test_common/src/http/client/nosend:http_client_nosend", "@com_google_googletest//:gtest_main", ], @@ -590,6 +601,7 @@ cc_test( deps = [ ":otlp_file_log_record_exporter", "//api", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -605,6 +617,7 @@ cc_test( deps = [ ":otlp_file_log_record_exporter", "//api", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -622,6 +635,7 @@ cc_test( ":otlp_grpc_log_record_exporter", "//api", "//sdk/src/logs", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -638,6 +652,7 @@ cc_test( ":otlp_grpc_log_record_exporter", "//api", "//sdk/src/logs", + "//sdk/src/metrics", "@com_google_googletest//:gtest_main", ], ) @@ -747,5 +762,6 @@ otel_cc_benchmark( deps = [ ":otlp_grpc_exporter", "//examples/common/foo_library:common_foo_library", + "//sdk/src/metrics", ], ) diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_metric_utils.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_metric_utils.h index 4f92b8e665..ba9b11e37d 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_metric_utils.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_metric_utils.h @@ -55,6 +55,10 @@ class OtlpMetricUtils static void ConvertHistogramMetric(const opentelemetry::sdk::metrics::MetricData &metric_data, proto::metrics::v1::Histogram *const histogram) noexcept; + static void ConvertExponentialHistogramMetric( + const opentelemetry::sdk::metrics::MetricData &metric_data, + proto::metrics::v1::ExponentialHistogram *const histogram) noexcept; + static void ConvertGaugeMetric(const opentelemetry::sdk::metrics::MetricData &metric_data, proto::metrics::v1::Gauge *const gauge) noexcept; diff --git a/exporters/otlp/src/otlp_metric_utils.cc b/exporters/otlp/src/otlp_metric_utils.cc index 65f86a4e47..5383c0d563 100644 --- a/exporters/otlp/src/otlp_metric_utils.cc +++ b/exporters/otlp/src/otlp_metric_utils.cc @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -12,6 +13,7 @@ #include "opentelemetry/exporters/otlp/otlp_preferred_temporality.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/metrics/data/circular_buffer.h" #include "opentelemetry/sdk/metrics/data/metric_data.h" #include "opentelemetry/sdk/metrics/data/point_data.h" #include "opentelemetry/sdk/metrics/export/metric_producer.h" @@ -63,6 +65,11 @@ metric_sdk::AggregationType OtlpMetricUtils::GetAggregationType( { return metric_sdk::AggregationType::kHistogram; } + else if (nostd::holds_alternative( + point_data_with_attributes.point_data)) + { + return metric_sdk::AggregationType::kBase2ExponentialHistogram; + } else if (nostd::holds_alternative( point_data_with_attributes.point_data)) { @@ -177,6 +184,70 @@ void OtlpMetricUtils::ConvertHistogramMetric( } } +void OtlpMetricUtils::ConvertExponentialHistogramMetric( + const metric_sdk::MetricData &metric_data, + proto::metrics::v1::ExponentialHistogram *const histogram) noexcept +{ + histogram->set_aggregation_temporality( + GetProtoAggregationTemporality(metric_data.aggregation_temporality)); + auto start_ts = metric_data.start_ts.time_since_epoch().count(); + auto ts = metric_data.end_ts.time_since_epoch().count(); + for (auto &point_data_with_attributes : metric_data.point_data_attr_) + { + proto::metrics::v1::ExponentialHistogramDataPoint *proto_histogram_point_data = + histogram->add_data_points(); + proto_histogram_point_data->set_start_time_unix_nano(start_ts); + proto_histogram_point_data->set_time_unix_nano(ts); + auto histogram_data = nostd::get( + point_data_with_attributes.point_data); + if (histogram_data.positive_buckets_ == nullptr && histogram_data.negative_buckets_ == nullptr) + { + continue; + } + // sum + proto_histogram_point_data->set_sum(histogram_data.sum_); + proto_histogram_point_data->set_count(histogram_data.count_); + if (histogram_data.record_min_max_) + { + proto_histogram_point_data->set_min(histogram_data.min_); + proto_histogram_point_data->set_max(histogram_data.max_); + } + // negative buckets + if (!histogram_data.negative_buckets_->Empty()) + { + auto negative_buckets = proto_histogram_point_data->mutable_negative(); + negative_buckets->set_offset(histogram_data.negative_buckets_->StartIndex()); + + for (auto index = histogram_data.negative_buckets_->StartIndex(); + index <= histogram_data.negative_buckets_->EndIndex(); ++index) + { + negative_buckets->add_bucket_counts(histogram_data.negative_buckets_->Get(index)); + } + } + // positive buckets + if (!histogram_data.positive_buckets_->Empty()) + { + auto positive_buckets = proto_histogram_point_data->mutable_positive(); + positive_buckets->set_offset(histogram_data.positive_buckets_->StartIndex()); + + for (auto index = histogram_data.positive_buckets_->StartIndex(); + index <= histogram_data.positive_buckets_->EndIndex(); ++index) + { + positive_buckets->add_bucket_counts(histogram_data.positive_buckets_->Get(index)); + } + } + proto_histogram_point_data->set_scale(histogram_data.scale_); + proto_histogram_point_data->set_zero_count(histogram_data.zero_count_); + + // attributes + for (auto &kv_attr : point_data_with_attributes.attributes) + { + OtlpPopulateAttributeUtils::PopulateAttribute(proto_histogram_point_data->add_attributes(), + kv_attr.first, kv_attr.second); + } + } +} + void OtlpMetricUtils::ConvertGaugeMetric(const opentelemetry::sdk::metrics::MetricData &metric_data, proto::metrics::v1::Gauge *const gauge) noexcept { @@ -225,6 +296,10 @@ void OtlpMetricUtils::PopulateInstrumentInfoMetrics( ConvertHistogramMetric(metric_data, metric->mutable_histogram()); break; } + case metric_sdk::AggregationType::kBase2ExponentialHistogram: { + ConvertExponentialHistogramMetric(metric_data, metric->mutable_exponential_histogram()); + break; + } case metric_sdk::AggregationType::kLastValue: { ConvertGaugeMetric(metric_data, metric->mutable_gauge()); break; diff --git a/exporters/otlp/test/otlp_metrics_serialization_test.cc b/exporters/otlp/test/otlp_metrics_serialization_test.cc index 66c1376d46..c7b72aa75d 100644 --- a/exporters/otlp/test/otlp_metrics_serialization_test.cc +++ b/exporters/otlp/test/otlp_metrics_serialization_test.cc @@ -15,6 +15,7 @@ #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/common/attribute_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/metrics/data/circular_buffer.h" #include "opentelemetry/sdk/metrics/data/metric_data.h" #include "opentelemetry/sdk/metrics/data/point_data.h" #include "opentelemetry/sdk/metrics/export/metric_producer.h" @@ -127,6 +128,60 @@ static metrics_sdk::MetricData CreateHistogramAggregationData() return data; } +static metrics_sdk::MetricData CreateExponentialHistogramAggregationData( + const std::chrono::system_clock::time_point &now_time) +{ + metrics_sdk::MetricData data; + data.start_ts = opentelemetry::common::SystemTimestamp(now_time); + metrics_sdk::InstrumentDescriptor inst_desc = {"Histogram", "desc", "unit", + metrics_sdk::InstrumentType::kHistogram, + metrics_sdk::InstrumentValueType::kDouble}; + metrics_sdk::Base2ExponentialHistogramPointData s_data_1, s_data_2; + s_data_1.count_ = 3; + s_data_1.sum_ = 6.5; + s_data_1.min_ = 0.0; + s_data_1.max_ = 3.5; + s_data_1.scale_ = 3; + s_data_1.record_min_max_ = true; + s_data_1.zero_count_ = 1; + s_data_1.positive_buckets_ = + std::make_unique(10); + s_data_1.negative_buckets_ = + std::make_unique(10); + s_data_1.positive_buckets_->Increment(1, 1); + s_data_1.negative_buckets_->Increment(-2, 1); + + s_data_2.count_ = 4; + s_data_2.sum_ = 6.2; + s_data_2.min_ = -0.03; + s_data_2.max_ = 3.5; + s_data_2.scale_ = 3; + s_data_2.record_min_max_ = false; + s_data_2.zero_count_ = 2; + s_data_2.positive_buckets_ = + std::make_unique(10); + s_data_2.negative_buckets_ = + std::make_unique(10); + s_data_2.positive_buckets_->Increment(3, 1); + s_data_2.negative_buckets_->Increment(-2, 1); + s_data_2.negative_buckets_->Increment(-4, 2); + + data.aggregation_temporality = metrics_sdk::AggregationTemporality::kCumulative; + data.end_ts = opentelemetry::common::SystemTimestamp(now_time); + data.instrument_descriptor = inst_desc; + metrics_sdk::PointDataAttributes point_data_attr_1, point_data_attr_2; + point_data_attr_1.attributes = {{"k1", "v1"}}; + point_data_attr_1.point_data = s_data_1; + + point_data_attr_2.attributes = {{"k2", "v2"}}; + point_data_attr_2.point_data = s_data_2; + std::vector point_data_attr; + point_data_attr.push_back(point_data_attr_1); + point_data_attr.push_back(point_data_attr_2); + data.point_data_attr_ = std::move(point_data_attr); + return data; +} + static metrics_sdk::MetricData CreateObservableGaugeAggregationData() { metrics_sdk::MetricData data; @@ -261,6 +316,65 @@ TEST(OtlpMetricSerializationTest, Histogram) EXPECT_EQ(1, 1); } +TEST(OtlpMetricSerializationTest, ExponentialHistogramAggregationData) +{ + const auto start_test_time = std::chrono::system_clock::now(); + const auto data = CreateExponentialHistogramAggregationData(start_test_time); + opentelemetry::proto::metrics::v1::ExponentialHistogram exponentialHistogram; + otlp_exporter::OtlpMetricUtils::ConvertExponentialHistogramMetric(data, &exponentialHistogram); + EXPECT_EQ(exponentialHistogram.aggregation_temporality(), + proto::metrics::v1::AggregationTemporality::AGGREGATION_TEMPORALITY_CUMULATIVE); + + EXPECT_EQ(exponentialHistogram.data_points_size(), 2); + // Point 1 + { + const auto &data_point1 = exponentialHistogram.data_points(0); + EXPECT_EQ(data_point1.count(), 3); + EXPECT_EQ(data_point1.sum(), 6.5); + EXPECT_EQ(data_point1.min(), 0.0); + EXPECT_EQ(data_point1.max(), 3.5); + EXPECT_EQ(data_point1.zero_count(), 1); + EXPECT_EQ(data_point1.scale(), 3); + EXPECT_EQ(data_point1.positive().offset(), 1); + EXPECT_EQ(data_point1.positive().bucket_counts_size(), 1); + EXPECT_EQ(data_point1.positive().bucket_counts(0), 1); + EXPECT_EQ(data_point1.negative().offset(), -2); + EXPECT_EQ(data_point1.negative().bucket_counts_size(), 1); + EXPECT_EQ(data_point1.negative().bucket_counts(0), 1); + + EXPECT_EQ(data_point1.attributes_size(), 1); + EXPECT_EQ(data_point1.attributes(0).key(), "k1"); + EXPECT_EQ(data_point1.attributes(0).value().string_value(), "v1"); + EXPECT_EQ(data_point1.start_time_unix_nano(), data.start_ts.time_since_epoch().count()); + EXPECT_EQ(data_point1.time_unix_nano(), data.end_ts.time_since_epoch().count()); + } + + // Point 2 + { + const auto &data_point2 = exponentialHistogram.data_points(1); + EXPECT_EQ(data_point2.count(), 4); + EXPECT_EQ(data_point2.sum(), 6.2); + EXPECT_EQ(data_point2.min(), 0.0); + EXPECT_EQ(data_point2.max(), 0.0); + EXPECT_EQ(data_point2.zero_count(), 2); + EXPECT_EQ(data_point2.scale(), 3); + EXPECT_EQ(data_point2.positive().offset(), 3); + EXPECT_EQ(data_point2.positive().bucket_counts_size(), 1); + EXPECT_EQ(data_point2.positive().bucket_counts(0), 1); + EXPECT_EQ(data_point2.negative().offset(), -4); + EXPECT_EQ(data_point2.negative().bucket_counts_size(), 3); + EXPECT_EQ(data_point2.negative().bucket_counts(0), 2); + EXPECT_EQ(data_point2.negative().bucket_counts(1), 0); + EXPECT_EQ(data_point2.negative().bucket_counts(2), 1); + EXPECT_EQ(data_point2.attributes(0).key(), "k2"); + EXPECT_EQ(data_point2.attributes(0).value().string_value(), "v2"); + EXPECT_EQ(data_point2.start_time_unix_nano(), data.start_ts.time_since_epoch().count()); + EXPECT_EQ(data_point2.time_unix_nano(), data.end_ts.time_since_epoch().count()); + } + + EXPECT_EQ(1, 1); +} + TEST(OtlpMetricSerializationTest, ObservableGauge) { metrics_sdk::MetricData data = CreateObservableGaugeAggregationData(); diff --git a/exporters/prometheus/src/exporter_utils.cc b/exporters/prometheus/src/exporter_utils.cc index c874faa654..7055e0d86c 100644 --- a/exporters/prometheus/src/exporter_utils.cc +++ b/exporters/prometheus/src/exporter_utils.cc @@ -562,6 +562,10 @@ metric_sdk::AggregationType PrometheusExporterUtils::getAggregationType( { return metric_sdk::AggregationType::kHistogram; } + else if (nostd::holds_alternative(point_type)) + { + return metric_sdk::AggregationType::kBase2ExponentialHistogram; + } else if (nostd::holds_alternative(point_type)) { return metric_sdk::AggregationType::kLastValue; diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation.h b/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation.h index a4ec9175c5..c932d5069c 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation.h @@ -3,8 +3,8 @@ #pragma once +#include #include - #include "opentelemetry/sdk/metrics/data/metric_data.h" #include "opentelemetry/version.h" diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h b/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h index f5a48d7ddb..0cae37f39e 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h @@ -24,6 +24,15 @@ class HistogramAggregationConfig : public AggregationConfig std::vector boundaries_; bool record_min_max_ = true; }; + +class Base2ExponentialHistogramAggregationConfig : public AggregationConfig +{ +public: + size_t max_buckets_ = 160; + int32_t max_scale_ = 20; + bool record_min_max_ = true; +}; + } // namespace metrics } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_aggregation.h b/sdk/include/opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_aggregation.h new file mode 100644 index 0000000000..9e927b56a6 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_aggregation.h @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/common/spin_lock_mutex.h" +#include "opentelemetry/sdk/metrics/aggregation/aggregation.h" +#include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" +#include "opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_indexer.h" +#include "opentelemetry/sdk/metrics/data/circular_buffer.h" +#include "opentelemetry/sdk/metrics/data/metric_data.h" +#include "opentelemetry/sdk/metrics/data/point_data.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +class Base2ExponentialHistogramAggregation : public Aggregation +{ +public: + Base2ExponentialHistogramAggregation(const AggregationConfig *aggregation_config = nullptr); + Base2ExponentialHistogramAggregation(const Base2ExponentialHistogramPointData &point_data); + Base2ExponentialHistogramAggregation(Base2ExponentialHistogramPointData &&point_data); + + void Aggregate(int64_t value, const PointAttributes &attributes = {}) noexcept override; + void Aggregate(double value, const PointAttributes &attributes = {}) noexcept override; + + /* Returns the result of merge of the existing aggregation with delta + * aggregation with same boundaries */ + std::unique_ptr Merge(const Aggregation &delta) const noexcept override; + + /* Returns the new delta aggregation by comparing existing aggregation with + * next aggregation with same boundaries. Data points for `next` aggregation + * (sum , bucket-counts) should be more than the current aggregation - which + * is the normal scenario as measurements values are monotonic increasing. + */ + std::unique_ptr Diff(const Aggregation &next) const noexcept override; + + PointType ToPoint() const noexcept override; + +private: + void AggregateIntoBuckets(std::unique_ptr &buckets, + double value) noexcept; + void Downscale(uint32_t by) noexcept; + + mutable opentelemetry::common::SpinLockMutex lock_; + Base2ExponentialHistogramPointData point_data_; + Base2ExponentialHistogramIndexer indexer_; + bool record_min_max_ = true; +}; + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_indexer.h b/sdk/include/opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_indexer.h index 14c00070b1..ba6904d319 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_indexer.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_indexer.h @@ -3,9 +3,9 @@ #pragma once -#include "opentelemetry/version.h" +#include -#include +#include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregation/default_aggregation.h b/sdk/include/opentelemetry/sdk/metrics/aggregation/default_aggregation.h index 9b1b1c2d7d..70f6f8ee74 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregation/default_aggregation.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregation/default_aggregation.h @@ -6,6 +6,7 @@ #include #include "opentelemetry/sdk/metrics/aggregation/aggregation.h" +#include "opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_aggregation.h" #include "opentelemetry/sdk/metrics/aggregation/drop_aggregation.h" #include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h" #include "opentelemetry/sdk/metrics/aggregation/lastvalue_aggregation.h" @@ -80,6 +81,10 @@ class DefaultAggregation return std::unique_ptr(new DoubleHistogramAggregation(aggregation_config)); } break; + case AggregationType::kBase2ExponentialHistogram: + return std::unique_ptr( + new Base2ExponentialHistogramAggregation(aggregation_config)); + break; case AggregationType::kLastValue: if (instrument_descriptor.value_type_ == InstrumentValueType::kLong) { @@ -139,6 +144,9 @@ class DefaultAggregation return std::unique_ptr( new DoubleHistogramAggregation(nostd::get(point_data))); } + case AggregationType::kBase2ExponentialHistogram: + return std::unique_ptr(new Base2ExponentialHistogramAggregation( + nostd::get(point_data))); case AggregationType::kLastValue: if (instrument_descriptor.value_type_ == InstrumentValueType::kLong) { diff --git a/sdk/include/opentelemetry/sdk/metrics/data/circular_buffer.h b/sdk/include/opentelemetry/sdk/metrics/data/circular_buffer.h index db4de054f4..01588f71d4 100644 --- a/sdk/include/opentelemetry/sdk/metrics/data/circular_buffer.h +++ b/sdk/include/opentelemetry/sdk/metrics/data/circular_buffer.h @@ -3,8 +3,6 @@ #pragma once -#include -#include #include #include @@ -139,12 +137,12 @@ class AdaptingCircularBufferCounter * * @return the number of recordings for the index, or 0 if the index is out of bounds. */ - uint64_t Get(int32_t index); + uint64_t Get(int32_t index) const; private: size_t ToBufferIndex(int32_t index) const; - static constexpr int32_t kNullIndex = std::numeric_limits::min(); + static constexpr int32_t kNullIndex = (std::numeric_limits::min)(); // Index of the first populated element, may be kNullIndex if container is empty. int32_t start_index_ = kNullIndex; diff --git a/sdk/include/opentelemetry/sdk/metrics/data/metric_data.h b/sdk/include/opentelemetry/sdk/metrics/data/metric_data.h index ad1084f581..30fa04e47f 100644 --- a/sdk/include/opentelemetry/sdk/metrics/data/metric_data.h +++ b/sdk/include/opentelemetry/sdk/metrics/data/metric_data.h @@ -18,8 +18,11 @@ namespace metrics { using PointAttributes = opentelemetry::sdk::common::OrderedAttributeMap; -using PointType = opentelemetry::nostd:: - variant; +using PointType = opentelemetry::nostd::variant; struct PointDataAttributes { diff --git a/sdk/include/opentelemetry/sdk/metrics/data/point_data.h b/sdk/include/opentelemetry/sdk/metrics/data/point_data.h index 32853316a5..df14e2d099 100644 --- a/sdk/include/opentelemetry/sdk/metrics/data/point_data.h +++ b/sdk/include/opentelemetry/sdk/metrics/data/point_data.h @@ -7,6 +7,7 @@ #include "opentelemetry/common/timestamp.h" #include "opentelemetry/nostd/variant.h" +#include "opentelemetry/sdk/metrics/data/circular_buffer.h" #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -17,7 +18,8 @@ namespace metrics using ValueType = nostd::variant; -// TODO: remove ctors and initializers from below classes when GCC<5 stops shipping on Ubuntu +// TODO: remove ctors and initializers from below classes when GCC<5 stops +// shipping on Ubuntu class SumPointData { @@ -64,6 +66,90 @@ class HistogramPointData bool record_min_max_ = true; }; +class Base2ExponentialHistogramPointData +{ +public: + Base2ExponentialHistogramPointData(Base2ExponentialHistogramPointData &&) noexcept = default; + Base2ExponentialHistogramPointData &operator=(Base2ExponentialHistogramPointData &&) noexcept = + default; + + Base2ExponentialHistogramPointData(const Base2ExponentialHistogramPointData &other) + : sum_(other.sum_), + min_(other.min_), + max_(other.max_), + zero_threshold_(other.zero_threshold_), + count_(other.count_), + zero_count_(other.zero_count_), + max_buckets_(other.max_buckets_), + scale_(other.scale_), + record_min_max_(other.record_min_max_) + { + if (other.positive_buckets_) + { + positive_buckets_ = std::make_unique(*other.positive_buckets_); + } + if (other.negative_buckets_) + { + negative_buckets_ = std::make_unique(*other.negative_buckets_); + } + } + + Base2ExponentialHistogramPointData &operator=(const Base2ExponentialHistogramPointData &other) + { + if (this != &other) + { + sum_ = other.sum_; + min_ = other.min_; + max_ = other.max_; + zero_threshold_ = other.zero_threshold_; + count_ = other.count_; + zero_count_ = other.zero_count_; + max_buckets_ = other.max_buckets_; + scale_ = other.scale_; + record_min_max_ = other.record_min_max_; + + if (other.positive_buckets_) + { + positive_buckets_ = + std::make_unique(*other.positive_buckets_); + } + else + { + positive_buckets_.reset(); + } + + if (other.negative_buckets_) + { + negative_buckets_ = + std::make_unique(*other.negative_buckets_); + } + else + { + negative_buckets_.reset(); + } + } + return *this; + } + + // Default constructor + Base2ExponentialHistogramPointData() = default; + + // Members + double sum_ = {}; + double min_ = {}; + double max_ = {}; + double zero_threshold_ = {}; + uint64_t count_ = {}; + uint64_t zero_count_ = {}; + std::unique_ptr positive_buckets_ = + std::make_unique(0); + std::unique_ptr negative_buckets_ = + std::make_unique(0); + size_t max_buckets_ = {}; + int32_t scale_ = {}; + bool record_min_max_ = true; +}; + class DropPointData { public: diff --git a/sdk/include/opentelemetry/sdk/metrics/instruments.h b/sdk/include/opentelemetry/sdk/metrics/instruments.h index 6473a267f2..9e3c843f5e 100644 --- a/sdk/include/opentelemetry/sdk/metrics/instruments.h +++ b/sdk/include/opentelemetry/sdk/metrics/instruments.h @@ -45,7 +45,8 @@ enum class AggregationType kHistogram, kLastValue, kSum, - kDefault + kDefault, + kBase2ExponentialHistogram }; enum class AggregationTemporality diff --git a/sdk/src/metrics/CMakeLists.txt b/sdk/src/metrics/CMakeLists.txt index 8a7b801b77..d205eeef2c 100644 --- a/sdk/src/metrics/CMakeLists.txt +++ b/sdk/src/metrics/CMakeLists.txt @@ -20,6 +20,7 @@ add_library( state/observable_registry.cc state/sync_metric_storage.cc state/temporal_metric_storage.cc + aggregation/base2_exponential_histogram_aggregation.cc aggregation/base2_exponential_histogram_indexer.cc aggregation/histogram_aggregation.cc aggregation/lastvalue_aggregation.cc diff --git a/sdk/src/metrics/aggregation/base2_exponential_histogram_aggregation.cc b/sdk/src/metrics/aggregation/base2_exponential_histogram_aggregation.cc new file mode 100644 index 0000000000..ea98c88a0b --- /dev/null +++ b/sdk/src/metrics/aggregation/base2_exponential_histogram_aggregation.cc @@ -0,0 +1,478 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/common/spin_lock_mutex.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/sdk/metrics/aggregation/aggregation.h" +#include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" +#include "opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_aggregation.h" +#include "opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_indexer.h" +#include "opentelemetry/sdk/metrics/data/circular_buffer.h" +#include "opentelemetry/sdk/metrics/data/metric_data.h" +#include "opentelemetry/sdk/metrics/data/point_data.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +namespace +{ + +uint32_t GetScaleReduction(int32_t start_index, int32_t end_index, size_t max_buckets) noexcept +{ + uint32_t scale_reduction = 0; + while (static_cast(end_index - start_index + 1) > max_buckets) + { + start_index >>= 1; + end_index >>= 1; + scale_reduction++; + } + return scale_reduction; +} + +void DownscaleBuckets(std::unique_ptr &buckets, uint32_t by) noexcept +{ + if (buckets->Empty()) + { + return; + } + + // We want to preserve other optimisations here as well, e.g. integer size. + // Instead of creating a new counter, we copy the existing one (for bucket size + // optimisations), and clear the values before writing the new ones. + // TODO(euroelessar): Do downscaling in-place. + auto new_buckets = std::make_unique(buckets->MaxSize()); + new_buckets->Clear(); + + for (auto i = buckets->StartIndex(); i <= buckets->EndIndex(); ++i) + { + const uint64_t count = buckets->Get(i); + if (count > 0) + { + new_buckets->Increment(i >> by, count); + } + } + buckets = std::move(new_buckets); +} + +} // namespace + +Base2ExponentialHistogramAggregation::Base2ExponentialHistogramAggregation( + const AggregationConfig *aggregation_config) +{ + const Base2ExponentialHistogramAggregationConfig default_config; + auto ac = static_cast(aggregation_config); + if (!ac) + { + ac = &default_config; + } + + point_data_.max_buckets_ = (std::max)(ac->max_buckets_, static_cast(2)); + point_data_.scale_ = ac->max_scale_; + point_data_.record_min_max_ = ac->record_min_max_; + point_data_.min_ = (std::numeric_limits::max)(); + point_data_.max_ = (std::numeric_limits::min)(); + + // Initialize buckets + point_data_.positive_buckets_ = + std::make_unique(point_data_.max_buckets_); + point_data_.negative_buckets_ = + std::make_unique(point_data_.max_buckets_); + + indexer_ = Base2ExponentialHistogramIndexer(point_data_.scale_); +} + +Base2ExponentialHistogramAggregation::Base2ExponentialHistogramAggregation( + const Base2ExponentialHistogramPointData &point_data) + : point_data_{}, indexer_(point_data.scale_), record_min_max_{point_data.record_min_max_} +{ + point_data_.sum_ = point_data.sum_; + point_data_.min_ = point_data.min_; + point_data_.max_ = point_data.max_; + point_data_.zero_threshold_ = point_data.zero_threshold_; + point_data_.count_ = point_data.count_; + point_data_.zero_count_ = point_data.zero_count_; + point_data_.max_buckets_ = point_data.max_buckets_; + point_data_.scale_ = point_data.scale_; + point_data_.record_min_max_ = point_data.record_min_max_; + + // Deep copy the unique_ptr members + if (point_data.positive_buckets_) + { + point_data_.positive_buckets_ = + std::make_unique(*point_data.positive_buckets_); + } + if (point_data.negative_buckets_) + { + point_data_.negative_buckets_ = + std::make_unique(*point_data.negative_buckets_); + } +} + +Base2ExponentialHistogramAggregation::Base2ExponentialHistogramAggregation( + Base2ExponentialHistogramPointData &&point_data) + : point_data_{std::move(point_data)}, + indexer_(point_data_.scale_), + record_min_max_{point_data_.record_min_max_} +{} + +void Base2ExponentialHistogramAggregation::Aggregate( + int64_t value, + const PointAttributes & /* attributes */) noexcept +{ + Aggregate(double(value)); +} + +void Base2ExponentialHistogramAggregation::Aggregate( + double value, + const PointAttributes & /* attributes */) noexcept +{ + const std::lock_guard locked(lock_); + point_data_.sum_ += value; + point_data_.count_++; + + if (record_min_max_) + { + point_data_.min_ = (std::min)(point_data_.min_, value); + point_data_.max_ = (std::max)(point_data_.max_, value); + } + + if (value == 0) + { + point_data_.zero_count_++; + return; + } + else if (value > 0) + { + if (point_data_.positive_buckets_) + { + AggregateIntoBuckets(point_data_.positive_buckets_, value); + } + } + else + { + if (point_data_.negative_buckets_) + { + AggregateIntoBuckets(point_data_.negative_buckets_, -value); + } + } +} + +void Base2ExponentialHistogramAggregation::AggregateIntoBuckets( + std::unique_ptr &buckets, + double value) noexcept +{ + if (!buckets) + { + buckets = std::make_unique(point_data_.max_buckets_); + } + + if (buckets->MaxSize() == 0) + { + buckets = std::make_unique(point_data_.max_buckets_); + } + + const int32_t index = indexer_.ComputeIndex(value); + if (!buckets->Increment(index, 1)) + { + const int32_t start_index = (std::min)(buckets->StartIndex(), index); + const int32_t end_index = (std::max)(buckets->EndIndex(), index); + const uint32_t scale_reduction = + GetScaleReduction(start_index, end_index, point_data_.max_buckets_); + Downscale(scale_reduction); + + buckets->Increment(index >> scale_reduction, 1); + } +} + +void Base2ExponentialHistogramAggregation::Downscale(uint32_t by) noexcept +{ + if (by == 0) + { + return; + } + + if (point_data_.positive_buckets_) + { + DownscaleBuckets(point_data_.positive_buckets_, by); + } + if (point_data_.negative_buckets_) + { + DownscaleBuckets(point_data_.negative_buckets_, by); + } + + point_data_.scale_ -= by; + indexer_ = Base2ExponentialHistogramIndexer(point_data_.scale_); +} + +// Merge A and B into a new circular buffer C. +// Caller must ensure that A and B are used as buckets at the same scale. +AdaptingCircularBufferCounter MergeBuckets(size_t max_buckets, + const AdaptingCircularBufferCounter &A, + const AdaptingCircularBufferCounter &B) +{ + AdaptingCircularBufferCounter C = AdaptingCircularBufferCounter(max_buckets); + C.Clear(); + + if (A.Empty() && B.Empty()) + { + return C; + } + if (A.Empty()) + { + return B; + } + if (B.Empty()) + { + return A; + } + + auto min_index = (std::min)(A.StartIndex(), B.StartIndex()); + auto max_index = (std::max)(A.EndIndex(), B.EndIndex()); + + for (int i = min_index; i <= max_index; i++) + { + auto count = A.Get(i) + B.Get(i); + if (count > 0) + { + C.Increment(i, count); + } + } + + return C; +} + +std::unique_ptr Base2ExponentialHistogramAggregation::Merge( + const Aggregation &delta) const noexcept +{ + auto left = nostd::get(ToPoint()); + auto right = nostd::get( + (static_cast(delta).ToPoint())); + + auto &low_res = left.scale_ < right.scale_ ? left : right; + auto &high_res = left.scale_ < right.scale_ ? right : left; + + Base2ExponentialHistogramPointData result_value; + result_value.count_ = low_res.count_ + high_res.count_; + result_value.sum_ = low_res.sum_ + high_res.sum_; + result_value.zero_count_ = low_res.zero_count_ + high_res.zero_count_; + result_value.scale_ = (std::min)(low_res.scale_, high_res.scale_); + result_value.max_buckets_ = + low_res.max_buckets_ >= high_res.max_buckets_ ? low_res.max_buckets_ : high_res.max_buckets_; + result_value.record_min_max_ = low_res.record_min_max_ && high_res.record_min_max_; + + if (result_value.record_min_max_) + { + result_value.min_ = (std::min)(low_res.min_, high_res.min_); + result_value.max_ = (std::max)(low_res.max_, high_res.max_); + } + + { + auto scale_reduction = high_res.scale_ - low_res.scale_; + + if (scale_reduction > 0) + { + DownscaleBuckets(high_res.positive_buckets_, scale_reduction); + DownscaleBuckets(high_res.negative_buckets_, scale_reduction); + high_res.scale_ -= scale_reduction; + } + } + + auto pos_min_index = + (std::min)(low_res.positive_buckets_->StartIndex(), high_res.positive_buckets_->StartIndex()); + auto pos_max_index = + (std::max)(low_res.positive_buckets_->EndIndex(), high_res.positive_buckets_->EndIndex()); + auto neg_min_index = + (std::min)(low_res.negative_buckets_->StartIndex(), high_res.negative_buckets_->StartIndex()); + auto neg_max_index = + (std::max)(low_res.negative_buckets_->EndIndex(), high_res.negative_buckets_->EndIndex()); + + if (static_cast(pos_max_index) > + static_cast(pos_min_index) + result_value.max_buckets_ || + static_cast(neg_max_index) > + static_cast(neg_min_index) + result_value.max_buckets_) + { + // We need to downscale the buckets to fit into the new max_buckets_. + const uint32_t scale_reduction = + GetScaleReduction(pos_min_index, pos_max_index, result_value.max_buckets_); + DownscaleBuckets(low_res.positive_buckets_, scale_reduction); + DownscaleBuckets(high_res.positive_buckets_, scale_reduction); + DownscaleBuckets(low_res.negative_buckets_, scale_reduction); + DownscaleBuckets(high_res.negative_buckets_, scale_reduction); + low_res.scale_ -= scale_reduction; + high_res.scale_ -= scale_reduction; + result_value.scale_ -= scale_reduction; + } + + result_value.positive_buckets_ = std::make_unique(MergeBuckets( + result_value.max_buckets_, *low_res.positive_buckets_, *high_res.positive_buckets_)); + result_value.negative_buckets_ = std::make_unique(MergeBuckets( + result_value.max_buckets_, *low_res.negative_buckets_, *high_res.negative_buckets_)); + + return std::unique_ptr{ + new Base2ExponentialHistogramAggregation(std::move(result_value))}; +} + +std::unique_ptr Base2ExponentialHistogramAggregation::Diff( + const Aggregation &next) const noexcept +{ + auto left = nostd::get(ToPoint()); + auto right = nostd::get( + (static_cast(next).ToPoint())); + + auto &low_res = left.scale_ < right.scale_ ? left : right; + auto &high_res = left.scale_ < right.scale_ ? right : left; + + { + const auto scale_reduction = high_res.scale_ - low_res.scale_; + + if (scale_reduction > 0) + { + if (high_res.positive_buckets_) + { + DownscaleBuckets(high_res.positive_buckets_, scale_reduction); + } + + if (high_res.negative_buckets_) + { + DownscaleBuckets(high_res.negative_buckets_, scale_reduction); + } + + high_res.scale_ -= scale_reduction; + } + } + + auto pos_min_index = + (left.positive_buckets_ && right.positive_buckets_) + ? (std::min)(left.positive_buckets_->StartIndex(), right.positive_buckets_->StartIndex()) + : 0; + auto pos_max_index = + (left.positive_buckets_ && right.positive_buckets_) + ? (std::max)(left.positive_buckets_->EndIndex(), right.positive_buckets_->EndIndex()) + : 0; + auto neg_min_index = + (left.negative_buckets_ && right.negative_buckets_) + ? (std::min)(left.negative_buckets_->StartIndex(), right.negative_buckets_->StartIndex()) + : 0; + auto neg_max_index = + (left.negative_buckets_ && right.negative_buckets_) + ? (std::max)(left.negative_buckets_->EndIndex(), right.negative_buckets_->EndIndex()) + : 0; + + if (static_cast(pos_max_index) > + static_cast(pos_min_index) + low_res.max_buckets_ || + static_cast(neg_max_index) > + static_cast(neg_min_index) + low_res.max_buckets_) + { + const uint32_t scale_reduction = + GetScaleReduction(pos_min_index, pos_max_index, low_res.max_buckets_); + + if (left.positive_buckets_) + { + DownscaleBuckets(left.positive_buckets_, scale_reduction); + } + if (right.positive_buckets_) + { + DownscaleBuckets(right.positive_buckets_, scale_reduction); + } + if (left.negative_buckets_) + { + DownscaleBuckets(left.negative_buckets_, scale_reduction); + } + if (right.negative_buckets_) + { + DownscaleBuckets(right.negative_buckets_, scale_reduction); + } + left.scale_ -= scale_reduction; + right.scale_ -= scale_reduction; + } + + Base2ExponentialHistogramPointData result_value; + result_value.scale_ = low_res.scale_; + result_value.max_buckets_ = low_res.max_buckets_; + result_value.record_min_max_ = false; + result_value.count_ = (right.count_ >= left.count_) ? (right.count_ - left.count_) : 0; + result_value.sum_ = (right.sum_ >= left.sum_) ? (right.sum_ - left.sum_) : 0.0; + result_value.zero_count_ = + (right.zero_count_ >= left.zero_count_) ? (right.zero_count_ - left.zero_count_) : 0; + + result_value.positive_buckets_ = + std::make_unique(right.max_buckets_); + result_value.negative_buckets_ = + std::make_unique(right.max_buckets_); + + if (left.positive_buckets_ && right.positive_buckets_) + { + for (auto i = pos_min_index; i <= pos_max_index; i++) + { + auto l_cnt = left.positive_buckets_->Get(i); + auto r_cnt = right.positive_buckets_->Get(i); + auto delta = (std::max)(static_cast(0), r_cnt - l_cnt); + if (delta > 0) + { + result_value.positive_buckets_->Increment(i, delta); + } + } + } + + if (left.negative_buckets_ && right.negative_buckets_) + { + for (auto i = neg_min_index; i <= neg_max_index; i++) + { + auto l_cnt = left.negative_buckets_->Get(i); + auto r_cnt = right.negative_buckets_->Get(i); + auto delta = (std::max)(static_cast(0), r_cnt - l_cnt); + if (delta > 0) + { + result_value.negative_buckets_->Increment(i, delta); + } + } + } + + return std::unique_ptr{ + new Base2ExponentialHistogramAggregation(std::move(result_value))}; +} + +PointType Base2ExponentialHistogramAggregation::ToPoint() const noexcept +{ + const std::lock_guard locked(lock_); + + Base2ExponentialHistogramPointData copy; + copy.sum_ = point_data_.sum_; + copy.min_ = point_data_.min_; + copy.max_ = point_data_.max_; + copy.zero_threshold_ = point_data_.zero_threshold_; + copy.count_ = point_data_.count_; + copy.zero_count_ = point_data_.zero_count_; + copy.max_buckets_ = point_data_.max_buckets_; + copy.scale_ = point_data_.scale_; + copy.record_min_max_ = point_data_.record_min_max_; + + if (point_data_.positive_buckets_) + { + copy.positive_buckets_ = + std::make_unique(*point_data_.positive_buckets_); + } + if (point_data_.negative_buckets_) + { + copy.negative_buckets_ = + std::make_unique(*point_data_.negative_buckets_); + } + + return copy; +} + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/metrics/data/circular_buffer.cc b/sdk/src/metrics/data/circular_buffer.cc index 007bedef6d..b6aab91a05 100644 --- a/sdk/src/metrics/data/circular_buffer.cc +++ b/sdk/src/metrics/data/circular_buffer.cc @@ -168,7 +168,7 @@ bool AdaptingCircularBufferCounter::Increment(int32_t index, uint64_t delta) return true; } -uint64_t AdaptingCircularBufferCounter::Get(int32_t index) +uint64_t AdaptingCircularBufferCounter::Get(int32_t index) const { if (index < start_index_ || index > end_index_) { diff --git a/sdk/test/metrics/aggregation_test.cc b/sdk/test/metrics/aggregation_test.cc index 92855863a8..85a01af589 100644 --- a/sdk/test/metrics/aggregation_test.cc +++ b/sdk/test/metrics/aggregation_test.cc @@ -1,18 +1,19 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#include "opentelemetry/sdk/metrics/aggregation/aggregation.h" #include #include +#include #include #include - -#include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/variant.h" -#include "opentelemetry/sdk/metrics/aggregation/aggregation.h" #include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" +#include "opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_aggregation.h" #include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h" #include "opentelemetry/sdk/metrics/aggregation/lastvalue_aggregation.h" #include "opentelemetry/sdk/metrics/aggregation/sum_aggregation.h" +#include "opentelemetry/sdk/metrics/data/circular_buffer.h" #include "opentelemetry/sdk/metrics/data/point_data.h" using namespace opentelemetry::sdk::metrics; @@ -223,3 +224,136 @@ TEST(Aggregation, DoubleHistogramAggregation) EXPECT_EQ(histogram_data.counts_[4], 0); // aggr2(28.1) - aggr1(25.1) EXPECT_EQ(histogram_data.counts_[7], 1); // aggr2(105.0) - aggr1(0) } + +TEST(Aggregation, Base2ExponentialHistogramAggregation) +{ + // Low res histo + auto SCALE0 = 0; + auto MAX_BUCKETS0 = 7; + Base2ExponentialHistogramAggregationConfig scale0_config; + scale0_config.max_scale_ = SCALE0; + scale0_config.max_buckets_ = MAX_BUCKETS0; + scale0_config.record_min_max_ = true; + Base2ExponentialHistogramAggregation scale0_aggr(&scale0_config); + auto point = scale0_aggr.ToPoint(); + ASSERT_TRUE(nostd::holds_alternative(point)); + auto histo_point = nostd::get(point); + EXPECT_EQ(histo_point.count_, 0); + EXPECT_EQ(histo_point.sum_, 0.0); + EXPECT_EQ(histo_point.zero_count_, 0); + EXPECT_EQ(histo_point.min_, (std::numeric_limits::max)()); + EXPECT_EQ(histo_point.max_, (std::numeric_limits::min)()); + EXPECT_EQ(histo_point.scale_, SCALE0); + EXPECT_EQ(histo_point.max_buckets_, MAX_BUCKETS0); + ASSERT_TRUE(histo_point.positive_buckets_ != nullptr); + ASSERT_TRUE(histo_point.negative_buckets_ != nullptr); + ASSERT_TRUE(histo_point.positive_buckets_->Empty()); + ASSERT_TRUE(histo_point.negative_buckets_->Empty()); + + // Create a new aggreagte based in point data + { + auto point_data = histo_point; + Base2ExponentialHistogramAggregation scale0_aggr2(point_data); + scale0_aggr2.Aggregate(0.0, {}); + + auto histo_point2 = nostd::get(point); + EXPECT_EQ(histo_point2.count_, 0); + EXPECT_EQ(histo_point2.sum_, 0.0); + EXPECT_EQ(histo_point2.zero_count_, 0); + EXPECT_EQ(histo_point2.min_, (std::numeric_limits::max)()); + EXPECT_EQ(histo_point2.max_, (std::numeric_limits::min)()); + EXPECT_EQ(histo_point2.scale_, SCALE0); + EXPECT_EQ(histo_point2.max_buckets_, MAX_BUCKETS0); + ASSERT_TRUE(histo_point2.positive_buckets_->Empty()); + ASSERT_TRUE(histo_point2.negative_buckets_->Empty()); + } + + // zero point + scale0_aggr.Aggregate(static_cast(0.0), {}); + histo_point = nostd::get(scale0_aggr.ToPoint()); + EXPECT_EQ(histo_point.count_, 1); + EXPECT_EQ(histo_point.zero_count_, 1); + + // Two recordings in the same bucket (bucket 1 at scale 0) + scale0_aggr.Aggregate(3.0, {}); + scale0_aggr.Aggregate(3.5, {}); + histo_point = nostd::get(scale0_aggr.ToPoint()); + EXPECT_EQ(histo_point.count_, 3); + EXPECT_EQ(histo_point.sum_, 6.5); + EXPECT_EQ(histo_point.min_, 0.0); + EXPECT_EQ(histo_point.max_, 3.5); + ASSERT_TRUE(histo_point.positive_buckets_ != nullptr); + ASSERT_TRUE(histo_point.negative_buckets_ != nullptr); + ASSERT_FALSE(histo_point.positive_buckets_->Empty()); + auto start_index = histo_point.positive_buckets_->StartIndex(); + auto end_index = histo_point.positive_buckets_->EndIndex(); + EXPECT_EQ(start_index, 1); + EXPECT_EQ(end_index, 1); + EXPECT_EQ(histo_point.positive_buckets_->Get(start_index), 2); + + // Recording in a different bucket (bucket -2 at scale 0) + scale0_aggr.Aggregate(-0.3, {}); + histo_point = nostd::get(scale0_aggr.ToPoint()); + EXPECT_EQ(histo_point.count_, 4); + EXPECT_EQ(histo_point.sum_, 6.2); + EXPECT_EQ(histo_point.min_, -0.3); + EXPECT_EQ(histo_point.max_, 3.5); + ASSERT_TRUE(histo_point.positive_buckets_ != nullptr); + ASSERT_TRUE(histo_point.negative_buckets_ != nullptr); + EXPECT_EQ(histo_point.negative_buckets_->Get(-2), 1); + EXPECT_EQ(histo_point.positive_buckets_->Get(1), 2); + + Base2ExponentialHistogramAggregationConfig scale1_config; + scale1_config.max_scale_ = 1; + scale1_config.max_buckets_ = 14; + scale1_config.record_min_max_ = true; + Base2ExponentialHistogramAggregation scale1_aggr(&scale1_config); + + scale1_aggr.Aggregate(0.0, {}); + scale1_aggr.Aggregate(3.0, {}); + scale1_aggr.Aggregate(3.5, {}); + scale1_aggr.Aggregate(0.3, {}); + auto scale1_point = nostd::get(scale1_aggr.ToPoint()); + EXPECT_EQ(scale1_point.count_, 4); + EXPECT_EQ(scale1_point.sum_, 6.8); + EXPECT_EQ(scale1_point.zero_count_, 1); + EXPECT_EQ(scale1_point.min_, 0.0); + EXPECT_EQ(scale1_point.max_, 3.5); + + // Merge test + auto merged = scale0_aggr.Merge(scale1_aggr); + auto merged_point = nostd::get(merged->ToPoint()); + EXPECT_EQ(merged_point.count_, 8); + EXPECT_EQ(merged_point.sum_, 13.0); + EXPECT_EQ(merged_point.zero_count_, 2); + EXPECT_EQ(merged_point.min_, -0.3); + EXPECT_EQ(merged_point.max_, 3.5); + EXPECT_EQ(merged_point.scale_, 0); + ASSERT_TRUE(merged_point.positive_buckets_ != nullptr); + ASSERT_TRUE(merged_point.negative_buckets_ != nullptr); + EXPECT_EQ(merged_point.positive_buckets_->Get(1), 4); + EXPECT_EQ(merged_point.negative_buckets_->Get(-2), 1); + EXPECT_EQ(merged_point.positive_buckets_->Get(2), 0); + + // Diff test + Base2ExponentialHistogramAggregation scale2_aggr(&scale1_config); + Base2ExponentialHistogramAggregation scale3_aggr(&scale1_config); + scale2_aggr.Aggregate(2.0, {}); + scale2_aggr.Aggregate(4.0, {}); + scale2_aggr.Aggregate(2.5, {}); + + scale3_aggr.Aggregate(2.0, {}); + scale3_aggr.Aggregate(2.3, {}); + scale3_aggr.Aggregate(2.5, {}); + scale3_aggr.Aggregate(4.0, {}); + + auto diffd = scale2_aggr.Diff(scale3_aggr); + auto diffd_point = nostd::get(diffd->ToPoint()); + EXPECT_EQ(diffd_point.count_, 1); + EXPECT_NEAR(diffd_point.sum_, 2.3, 1e-9); + EXPECT_EQ(diffd_point.zero_count_, 0); + EXPECT_EQ(diffd_point.scale_, 1); + ASSERT_TRUE(diffd_point.positive_buckets_ != nullptr); + ASSERT_TRUE(diffd_point.negative_buckets_ != nullptr); + EXPECT_EQ(diffd_point.positive_buckets_->Get(2), 1); +} diff --git a/sdk/test/metrics/histogram_aggregation_benchmark.cc b/sdk/test/metrics/histogram_aggregation_benchmark.cc index b66538042c..1884167297 100644 --- a/sdk/test/metrics/histogram_aggregation_benchmark.cc +++ b/sdk/test/metrics/histogram_aggregation_benchmark.cc @@ -7,24 +7,29 @@ #include #include #include +#include #include #include #include #include "common.h" - #include "opentelemetry/context/context.h" #include "opentelemetry/metrics/meter.h" #include "opentelemetry/metrics/sync_instruments.h" -#include "opentelemetry/nostd/function_ref.h" #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" #include "opentelemetry/sdk/metrics/data/metric_data.h" #include "opentelemetry/sdk/metrics/data/point_data.h" #include "opentelemetry/sdk/metrics/export/metric_producer.h" +#include "opentelemetry/sdk/metrics/instruments.h" #include "opentelemetry/sdk/metrics/meter_provider.h" #include "opentelemetry/sdk/metrics/metric_reader.h" #include "opentelemetry/sdk/metrics/push_metric_exporter.h" +#include "opentelemetry/sdk/metrics/view/instrument_selector.h" +#include "opentelemetry/sdk/metrics/view/meter_selector.h" +#include "opentelemetry/sdk/metrics/view/view.h" +#include "opentelemetry/sdk/metrics/view/view_registry.h" using namespace opentelemetry; using namespace opentelemetry::sdk::instrumentationscope; @@ -32,9 +37,11 @@ using namespace opentelemetry::sdk::metrics; namespace { -void BM_HistogramAggregation(benchmark::State &state) + +template +void HistogramAggregation(benchmark::State &state, std::unique_ptr views) { - MeterProvider mp; + MeterProvider mp(std::move(views)); auto m = mp.GetMeter("meter1", "version1", "schema1"); std::unique_ptr exporter(new MockMetricExporter()); @@ -50,7 +57,7 @@ void BM_HistogramAggregation(benchmark::State &state) { measurements[i] = static_cast(distribution(generator)); } - std::vector actuals; + std::vector actuals; std::vector collectionThreads; std::function collectMetrics = [&reader, &actuals]() { reader->Collect([&](ResourceMetrics &rm) { @@ -60,7 +67,7 @@ void BM_HistogramAggregation(benchmark::State &state) { for (const PointDataAttributes &dp : md.point_data_attr_) { - actuals.push_back(opentelemetry::nostd::get(dp.point_data)); + actuals.push_back(opentelemetry::nostd::get(dp.point_data)); } } } @@ -68,7 +75,7 @@ void BM_HistogramAggregation(benchmark::State &state) }); }; - while (state.KeepRunning()) + while (state.KeepRunningBatch(TOTAL_MEASUREMENTS)) { for (size_t i = 0; i < TOTAL_MEASUREMENTS; i++) { @@ -85,7 +92,74 @@ void BM_HistogramAggregation(benchmark::State &state) } } +void BM_HistogramAggregation(benchmark::State &state) +{ + std::unique_ptr views{new ViewRegistry()}; + HistogramAggregation(state, std::move(views)); +} + BENCHMARK(BM_HistogramAggregation); +// Add this helper function before your benchmark functions + +void RunBase2ExponentialHistogramAggregation(benchmark::State &state, int scale) +{ + std::string instrument_unit = "histogram1_unit"; + std::unique_ptr histogram_instrument_selector{ + new InstrumentSelector(InstrumentType::kHistogram, ".*", instrument_unit)}; + std::unique_ptr histogram_meter_selector{ + new MeterSelector("meter1", "version1", "schema1")}; + + Base2ExponentialHistogramAggregationConfig config; + config.max_scale_ = scale; + + std::unique_ptr histogram_view{ + new View("base2_expohisto", "description", instrument_unit, + AggregationType::kBase2ExponentialHistogram, + std::make_shared(config))}; + + std::unique_ptr views{new ViewRegistry()}; + views->AddView(std::move(histogram_instrument_selector), std::move(histogram_meter_selector), + std::move(histogram_view)); + + HistogramAggregation(state, std::move(views)); +} + +void BM_Base2ExponentialHistogramAggregationZeroScale(benchmark::State &state) +{ + RunBase2ExponentialHistogramAggregation(state, 0); +} +BENCHMARK(BM_Base2ExponentialHistogramAggregationZeroScale); + +void BM_Base2ExponentialHistogramAggregationOneScale(benchmark::State &state) +{ + RunBase2ExponentialHistogramAggregation(state, 1); +} +BENCHMARK(BM_Base2ExponentialHistogramAggregationOneScale); + +void BM_Base2ExponentialHistogramAggregationTwoScale(benchmark::State &state) +{ + RunBase2ExponentialHistogramAggregation(state, 2); +} +BENCHMARK(BM_Base2ExponentialHistogramAggregationTwoScale); + +void BM_Base2ExponentialHistogramAggregationFourScale(benchmark::State &state) +{ + RunBase2ExponentialHistogramAggregation(state, 4); +} +BENCHMARK(BM_Base2ExponentialHistogramAggregationFourScale); + +void BM_Base2ExponentialHistogramAggregationEightScale(benchmark::State &state) +{ + RunBase2ExponentialHistogramAggregation(state, 8); +} +BENCHMARK(BM_Base2ExponentialHistogramAggregationEightScale); + +void BM_Base2ExponentialHistogramAggregationSixteenScale(benchmark::State &state) +{ + RunBase2ExponentialHistogramAggregation(state, 16); +} +BENCHMARK(BM_Base2ExponentialHistogramAggregationSixteenScale); + } // namespace BENCHMARK_MAIN(); diff --git a/sdk/test/metrics/sync_metric_storage_histogram_test.cc b/sdk/test/metrics/sync_metric_storage_histogram_test.cc index ecaf527942..89dedb397f 100644 --- a/sdk/test/metrics/sync_metric_storage_histogram_test.cc +++ b/sdk/test/metrics/sync_metric_storage_histogram_test.cc @@ -18,6 +18,7 @@ #include "opentelemetry/nostd/function_ref.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/variant.h" +#include "opentelemetry/sdk/metrics/data/circular_buffer.h" #include "opentelemetry/sdk/metrics/data/metric_data.h" #include "opentelemetry/sdk/metrics/data/point_data.h" #include "opentelemetry/sdk/metrics/instruments.h" @@ -322,7 +323,220 @@ TEST_P(WritableMetricStorageHistogramTestFixture, DoubleHistogram) }); EXPECT_EQ(count_attributes, 2); // GET and PUT } + INSTANTIATE_TEST_SUITE_P(WritableMetricStorageHistogramTestDouble, WritableMetricStorageHistogramTestFixture, ::testing::Values(AggregationTemporality::kCumulative, AggregationTemporality::kDelta)); + +TEST_P(WritableMetricStorageHistogramTestFixture, Base2ExponentialDoubleHistogram) +{ + AggregationTemporality temporality = GetParam(); + auto sdk_start_ts = std::chrono::system_clock::now(); + double expected_total_get_requests = 0; + double expected_total_put_requests = 0; + InstrumentDescriptor instr_desc = {"name", "desc", "1unit", InstrumentType::kHistogram, + InstrumentValueType::kDouble}; + std::map attributes_get = {{"RequestType", "GET"}}; + std::map attributes_put = {{"RequestType", "PUT"}}; + + std::unique_ptr default_attributes_processor{ + new DefaultAttributesProcessor{}}; + opentelemetry::sdk::metrics::SyncMetricStorage storage( + instr_desc, AggregationType::kBase2ExponentialHistogram, default_attributes_processor.get(), +#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW + ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), +#endif + nullptr); + + storage.RecordDouble(10.0, + KeyValueIterableView>(attributes_get), + opentelemetry::context::Context{}); + expected_total_get_requests += 10; + + storage.RecordDouble(30.0, + KeyValueIterableView>(attributes_put), + opentelemetry::context::Context{}); + expected_total_put_requests += 30; + + storage.RecordDouble(20.0, + KeyValueIterableView>(attributes_get), + opentelemetry::context::Context{}); + expected_total_get_requests += 20; + + storage.RecordDouble(40.0, + KeyValueIterableView>(attributes_put), + opentelemetry::context::Context{}); + expected_total_put_requests += 40; + + std::shared_ptr collector(new MockCollectorHandle(temporality)); + std::vector> collectors; + collectors.push_back(collector); + + // Some computation here + auto collection_ts = std::chrono::system_clock::now(); + size_t count_attributes = 0; + storage.Collect( + collector.get(), collectors, sdk_start_ts, collection_ts, [&](const MetricData &metric_data) { + for (const auto &data_attr : metric_data.point_data_attr_) + { + const auto &data = + opentelemetry::nostd::get(data_attr.point_data); + if (opentelemetry::nostd::get( + data_attr.attributes.find("RequestType")->second) == "GET") + { + EXPECT_EQ(data.sum_, expected_total_get_requests); + EXPECT_EQ(data.count_, 2); + EXPECT_EQ(data.min_, 10); + EXPECT_EQ(data.max_, 20); + EXPECT_EQ(data.negative_buckets_->Empty(), true); + auto start_index = data.positive_buckets_->StartIndex(); + auto end_index = data.positive_buckets_->EndIndex(); + EXPECT_EQ(data.positive_buckets_->Get(start_index), 1); + EXPECT_EQ(data.positive_buckets_->Get(end_index), 1); + count_attributes++; + } + else if (opentelemetry::nostd::get( + data_attr.attributes.find("RequestType")->second) == "PUT") + { + EXPECT_EQ(data.sum_, expected_total_put_requests); + EXPECT_EQ(data.count_, 2); + EXPECT_EQ(data.min_, 30); + EXPECT_EQ(data.max_, 40); + auto start_index = data.positive_buckets_->StartIndex(); + auto end_index = data.positive_buckets_->EndIndex(); + EXPECT_EQ(data.positive_buckets_->Get(start_index), 1); + EXPECT_EQ(data.positive_buckets_->Get(end_index), 1); + EXPECT_EQ(data.negative_buckets_->Empty(), true); + count_attributes++; + } + } + return true; + }); + EXPECT_EQ(count_attributes, 2); // GET and PUT + + // In case of delta temporarily, subsequent collection would contain new data points, so resetting + // the counts + if (temporality == AggregationTemporality::kDelta) + { + expected_total_get_requests = 0; + expected_total_put_requests = 0; + } + + // collect one more time. + collection_ts = std::chrono::system_clock::now(); + count_attributes = 0; + storage.Collect( + collector.get(), collectors, sdk_start_ts, collection_ts, [&](const MetricData &metric_data) { + if (temporality == AggregationTemporality::kCumulative) + { + EXPECT_EQ(metric_data.start_ts, sdk_start_ts); + } + for (const auto &data_attr : metric_data.point_data_attr_) + { + const auto &data = + opentelemetry::nostd::get(data_attr.point_data); + if (opentelemetry::nostd::get( + data_attr.attributes.find("RequestType")->second) == "GET") + { + count_attributes++; + EXPECT_EQ(data.sum_, expected_total_get_requests); + } + else if (opentelemetry::nostd::get( + data_attr.attributes.find("RequestType")->second) == "PUT") + { + count_attributes++; + EXPECT_EQ(data.sum_, expected_total_put_requests); + } + } + return true; + }); + if (temporality == AggregationTemporality::kCumulative) + { + EXPECT_EQ(count_attributes, 2); // GET AND PUT + } + + storage.RecordDouble(50.0, + KeyValueIterableView>(attributes_get), + opentelemetry::context::Context{}); + expected_total_get_requests += 50; + storage.RecordDouble(40.0, + KeyValueIterableView>(attributes_put), + opentelemetry::context::Context{}); + expected_total_put_requests += 40; + + collection_ts = std::chrono::system_clock::now(); + count_attributes = 0; + storage.Collect( + collector.get(), collectors, sdk_start_ts, collection_ts, [&](const MetricData &metric_data) { + for (const auto &data_attr : metric_data.point_data_attr_) + { + auto &data = + opentelemetry::nostd::get(data_attr.point_data); + if (opentelemetry::nostd::get( + data_attr.attributes.find("RequestType")->second) == "GET") + { + EXPECT_EQ(data.sum_, expected_total_get_requests); + count_attributes++; + auto start_index = data.positive_buckets_->StartIndex(); + auto end_index = data.positive_buckets_->EndIndex(); + if (temporality == AggregationTemporality::kCumulative) + { + EXPECT_EQ(data.count_, 3); + EXPECT_EQ(data.min_, 10); + EXPECT_EQ(data.max_, 50); + uint64_t count = 0; + for (auto i = start_index; i <= end_index; ++i) + { + count += data.positive_buckets_->Get(i); + } + EXPECT_EQ(count, 3); + } + if (temporality == AggregationTemporality::kDelta) + { + EXPECT_EQ(data.count_, 1); + EXPECT_EQ(data.min_, 50); + EXPECT_EQ(data.max_, 50); + EXPECT_EQ(data.positive_buckets_->Get(start_index), 1); + EXPECT_EQ(end_index, start_index); + } + } + else if (opentelemetry::nostd::get( + data_attr.attributes.find("RequestType")->second) == "PUT") + { + EXPECT_EQ(data.sum_, expected_total_put_requests); + count_attributes++; + auto start_index = data.positive_buckets_->StartIndex(); + auto end_index = data.positive_buckets_->EndIndex(); + if (temporality == AggregationTemporality::kCumulative) + { + EXPECT_EQ(data.count_, 3); + EXPECT_EQ(data.min_, 30); + EXPECT_EQ(data.max_, 40); + uint64_t count = 0; + for (auto i = start_index; i <= end_index; ++i) + { + count += data.positive_buckets_->Get(i); + } + EXPECT_EQ(count, 3); + } + if (temporality == AggregationTemporality::kDelta) + { + EXPECT_EQ(data.count_, 1); + EXPECT_EQ(data.min_, 40); + EXPECT_EQ(data.max_, 40); + EXPECT_EQ(data.positive_buckets_->Get(start_index), 1); + EXPECT_EQ(end_index, start_index); + } + } + } + return true; + }); + + EXPECT_EQ(count_attributes, 2); // GET and PUT +} + +INSTANTIATE_TEST_SUITE_P(WritableMetricStorageHistogramTestBase2ExponentialDouble, + WritableMetricStorageHistogramTestFixture, + ::testing::Values(AggregationTemporality::kCumulative, + AggregationTemporality::kDelta));